summaryrefslogtreecommitdiff
path: root/utils
diff options
context:
space:
mode:
authorBehdad Esfahbod <behdad@behdad.org>2018-07-08 14:45:32 +0200
committerBehdad Esfahbod <behdad@behdad.org>2018-07-08 14:45:32 +0200
commit899841121260d62fc3f1c1728f82f5c02185dd97 (patch)
tree643abf1f58c3b627b786d3b89a103a0df956172f /utils
parent5ec478dac9146175de7ba4a1f88ebbab963f4c0f (diff)
downloadpango-899841121260d62fc3f1c1728f82f5c02185dd97.tar.gz
Rename pango-view/ dir to utils/
Diffstat (limited to 'utils')
-rw-r--r--utils/EMOJI-MODIFIERS.txt71
-rw-r--r--utils/GLASS.txt168
-rw-r--r--utils/HELLO.txt54
-rw-r--r--utils/Makefile.am104
-rw-r--r--utils/meson.build58
-rw-r--r--utils/pango-view.c23
-rw-r--r--utils/test-arabic.txt7
-rw-r--r--utils/test-chinese.txt21
-rw-r--r--utils/test-devanagari.txt9
-rw-r--r--utils/test-feature-tag.markup130
-rw-r--r--utils/test-font-variations.markup9
-rw-r--r--utils/test-gurmukhi.txt26
-rw-r--r--utils/test-hebrew.txt13
-rw-r--r--utils/test-ipa.txt8
-rw-r--r--utils/test-justify.txt5
-rw-r--r--utils/test-lao.txt2
-rw-r--r--utils/test-latin.txt9
-rw-r--r--utils/test-long-paragraph.txt18
-rw-r--r--utils/test-mixed.markup9
-rw-r--r--utils/test-mixed.txt9
-rw-r--r--utils/test-mongolian.txt15
-rw-r--r--utils/test-nko.txt15
-rw-r--r--utils/test-opentype-language.markup6
-rw-r--r--utils/test-syriac.txt18
-rw-r--r--utils/test-tamil.txt13
-rw-r--r--utils/test-thai.txt11
-rw-r--r--utils/test-tibetan.txt12
-rw-r--r--utils/viewer-cairo.c367
-rw-r--r--utils/viewer-cairo.h47
-rw-r--r--utils/viewer-main.c182
-rw-r--r--utils/viewer-pangocairo.c462
-rw-r--r--utils/viewer-pangoft2.c162
-rw-r--r--utils/viewer-pangoxft.c154
-rw-r--r--utils/viewer-render.c823
-rw-r--r--utils/viewer-render.h90
-rw-r--r--utils/viewer-x.c234
-rw-r--r--utils/viewer-x.h70
-rw-r--r--utils/viewer.h99
38 files changed, 3533 insertions, 0 deletions
diff --git a/utils/EMOJI-MODIFIERS.txt b/utils/EMOJI-MODIFIERS.txt
new file mode 100644
index 00000000..11101e5f
--- /dev/null
+++ b/utils/EMOJI-MODIFIERS.txt
@@ -0,0 +1,71 @@
+ 👍 👎 👌 👊 ✊ ✌ 👋 ✋ 👐 👆 👇 👉 👈 🙌 🙏 ☝ 👏 💪 💅 ⬇ ⬅ ➡ ↗ ↖ ↘ ↙ ↔
+↕ 🔄 ◀ ▶ 🔼 🔽 ↩ ↪ ⏪ ⏩ ⏫ ⏬ ⤵ ⤴ 🔀 🔁 🔂 🔝 🔚 🔙 🔛 🔜 🔃 🔺 🔻 ⬆
+#️⃣*️⃣0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣ 🇮🇷 👩‍👩‍👧‍👦 👨‍❤️‍👨 👨‍❤️‍💋‍👨 🤹🏾‍♀️ 0︎ 0️
+🙂🙂︎🙂️
+
+☝☝🏻☝🏼☝🏽☝🏾☝🏿 261D White up pointing index
+⛹⛹🏻⛹🏼⛹🏽⛹🏾⛹🏿 26F9 Person with ball
+✊✊🏻✊🏼✊🏽✊🏾✊🏿 270A Raised fist
+✋✋🏻✋🏼✋🏽✋🏾✋🏿 270B Raised hand
+✌✌🏻✌🏼✌🏽✌🏾✌🏿 270C Victory hand
+✍✍🏻✍🏼✍🏽✍🏾✍🏿 270D Writing hand
+🎅🎅🏻🎅🏼🎅🏽🎅🏾🎅🏿 1F385 Father Christmas
+🏃🏃🏻🏃🏼🏃🏽🏃🏾🏃🏿 1F3C3 Runner
+🏄🏄🏻🏄🏼🏄🏽🏄🏾🏄🏿 1F3C4 Surfer
+🏊🏊🏻🏊🏼🏊🏽🏊🏾🏊🏿 1F3CA Swimmer
+🏋🏋🏻🏋🏼🏋🏽🏋🏾🏋🏿 1F3CB Weight lifter
+👂👂🏻👂🏼👂🏽👂🏾👂🏿 1F442 Ear
+👃👃🏻👃🏼👃🏽👃🏾👃🏿 1F443 Nose
+👆👆🏻👆🏼👆🏽👆🏾👆🏿 1F446 White up pointing backhand index
+👇👇🏻👇🏼👇🏽👇🏾👇🏿 1F447 White down pointing backhand index
+👈👈🏻👈🏼👈🏽👈🏾👈🏿 1F448 White left pointing backhand index
+👉👉🏻👉🏼👉🏽👉🏾👉🏿 1F449 White right pointing backhand index
+👊👊🏻👊🏼👊🏽👊🏾👊🏿 1F44A Fisted hand sign
+👋👋🏻👋🏼👋🏽👋🏾👋🏿 1F44B Waving hand sign
+👌👌🏻👌🏼👌🏽👌🏾👌🏿 1F44C OK hand sign
+👍👍🏻👍🏼👍🏽👍🏾👍🏿 1F44D Thumbs up sign
+👎👎🏻👎🏼👎🏽👎🏾👎🏿 1F44E Thumbs down sign
+👏👏🏻👏🏼👏🏽👏🏾👏🏿 1F44F Clapping hands sign
+👐👐🏻👐🏼👐🏽👐🏾👐🏿 1F450 Open hands sign
+👦👦🏻👦🏼👦🏽👦🏾👦🏿 1F466 Boy
+👧👧🏻👧🏼👧🏽👧🏾👧🏿 1F467 Girl
+👨👨🏻👨🏼👨🏽👨🏾👨🏿 1F468 Man
+👩👩🏻👩🏼👩🏽👩🏾👩🏿 1F469 Woman
+👮👮🏻👮🏼👮🏽👮🏾👮🏿 1F46E Police officer
+👰👰🏻👰🏼👰🏽👰🏾👰🏿 1F470 Bride with veil
+👱👱🏻👱🏼👱🏽👱🏾👱🏿 1F471 Person with blond hair
+👲👲🏻👲🏼👲🏽👲🏾👲🏿 1F472 Man with Gua Pi Mao
+👳👳🏻👳🏼👳🏽👳🏾👳🏿 1F473 Man with Turban
+👴👴🏻👴🏼👴🏽👴🏾👴🏿 1F474 Older man
+👵👵🏻👵🏼👵🏽👵🏾👵🏿 1F475 Older woman
+👶👶🏻👶🏼👶🏽👶🏾👶🏿 1F476 Baby
+👷👷🏻👷🏼👷🏽👷🏾👷🏿 1F477 Construction worker
+👸👸🏻👸🏼👸🏽👸🏾👸🏿 1F478 Princess
+👼👼🏻👼🏼👼🏽👼🏾👼🏿 1F47C Baby angel
+💁💁🏻💁🏼💁🏽💁🏾💁🏿 1F481 Information desk person
+💂💂🏻💂🏼💂🏽💂🏾💂🏿 1F482 Guardsman
+💃💃🏻💃🏼💃🏽💃🏾💃🏿 1F483 Dancer
+💅💅🏻💅🏼💅🏽💅🏾💅🏿 1F485 Nail polish
+💆💆🏻💆🏼💆🏽💆🏾💆🏿 1F486 Face massage
+💇💇🏻💇🏼💇🏽💇🏾💇🏿 1F487 Haircut
+💪💪🏻💪🏼💪🏽💪🏾💪🏿 1F4AA Flexed Biceps
+🕵🕵🏻🕵🏼🕵🏽🕵🏾🕵🏿 1F575 Sleuth or spy
+🖐🖐🏻🖐🏼🖐🏽🖐🏾🖐🏿 1F590 Raised hand with finder splayed
+🖕🖕🏻🖕🏼🖕🏽🖕🏾🖕🏿 1F595 Reversed hand with middle finder
+🖖🖖🏻🖖🏼🖖🏽🖖🏾🖖🏿 1F596 Raised hand with part between middle and ring fingers
+🙅🙅🏻🙅🏼🙅🏽🙅🏾🙅🏿 1F645 Face with no good gesture
+🙆🙆🏻🙆🏼🙆🏽🙆🏾🙆🏿 1F646 Face with OK gesture
+🙇🙇🏻🙇🏼🙇🏽🙇🏾🙇🏿 1F647 Person bowing deeply
+🙋🙋🏻🙋🏼🙋🏽🙋🏾🙋🏿 1F64B Happy person raising one hand
+🙌🙌🏻🙌🏼🙌🏽🙌🏾🙌🏿 1F64C Person raising both hands in celebration
+🙍🙍🏻🙍🏼🙍🏽🙍🏾🙍🏿 1F64D Person frowning
+🙎🙎🏻🙎🏼🙎🏽🙎🏾🙎🏿 1F64E Person with pouting face
+🙏🙏🏻🙏🏼🙏🏽🙏🏾🙏🏿 1F64F Person wiht folded hands
+🚣🚣🏻🚣🏼🚣🏽🚣🏾🚣🏿 1F6A3 Rowboat
+🚴🚴🏻🚴🏼🚴🏽🚴🏾🚴🏿 1F6B4 Bicyclist
+🚵🚵🏻🚵🏼🚵🏽🚵🏾🚵🏿 1F6B5 Mountain bicyclist
+🚶🚶🏻🚶🏼🚶🏽🚶🏾🚶🏿 1F6B6 Pedestrian
+🛀🛀🏻🛀🏼🛀🏽🛀🏾🛀🏿 1F6C0 Bath
+🤘🤘🏻🤘🏼🤘🏽🤘🏾🤘🏿 1F918 Sign of the horns
+
+Data source: UTR #51, Version 5
diff --git a/utils/GLASS.txt b/utils/GLASS.txt
new file mode 100644
index 00000000..8eec3344
--- /dev/null
+++ b/utils/GLASS.txt
@@ -0,0 +1,168 @@
+I Can Eat Glass
+In various languages
+
+Adopted from http://www.columbia.edu/kermit/utf8.html#glass
+Do not edit. Submit additions to the URL above and resynch.
+
+Permission is granted by the Kermit project (http://www.columbia.edu/kermit/)
+to redistribute this file, with absolutely no warranty.
+
+
+
+Sanskrit: काचं शक्नोम्यत्तुम् । नोपहिनस्ति माम् ॥
+Sanskrit (standard transcription): kācaṃ śaknomyattum; nopahinasti mām.
+Classical Greek: ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει.
+Greek: Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα.
+Etruscan: (NEEDED)
+Latin: Vitrum edere possum; mihi non nocet.
+Old French: Je puis mangier del voirre. Ne me nuit.
+French: Je peux manger du verre, ça ne me fait pas de mal.
+Provençal / Occitan: Pòdi manjar de veire, me nafrariá pas.
+Québécois: J'peux manger d'la vitre, ça m'fa pas mal.
+Walloon: Dji pou magnî do vêre, çoula m' freut nén må.
+Champenois: (NEEDED)
+Lorrain: (NEEDED)
+Picard: Ch'peux mingi du verre, cha m'foé mie n'ma.
+Corsican: (NEEDED)
+Kreyòl Ayisyen: Mwen kap manje vè, li pa blese'm.
+Basque: Kristala jan dezaket, ez dit minik ematen.
+Catalan: Puc menjar vidre que no em fa mal.
+Spanish: Puedo comer vidrio, no me hace daño.
+Aragones: Puedo minchar beire, no me'n fa mal .
+Galician: Eu podo xantar cristais e non cortarme.
+Portuguese: Posso comer vidro, não me faz mal.
+Brazilian Portuguese: Posso comer vidro, não me machuca.
+Caboverdiano: M' podê cumê vidru, ca ta maguâ-m'.
+Papiamentu: Ami por kome glas anto e no ta hasimi daño.
+Italian: Posso mangiare il vetro e non mi fa male.
+Milanese: Sôn bôn de magnà el véder, el me fa minga mal.
+Roman: Me posso magna' er vetro, e nun me fa male.
+Napoletano / Neapolitan: M' pozz magna' o'vetr, e nun m' fa mal.
+Sicilian: Puotsu mangiari u vitru, nun mi fa mali.
+Venetian: Mi posso magnare el vetro, no'l me fa mae.
+Zeneise (Genovese): Pòsso mangiâ o veddro e o no me fà mâ.
+Rheto-Romance / Romansch: (NEEDED)
+Romany / Tsigane: (NEEDED)
+Romanian: Pot să mănânc sticlă și ea nu mă rănește.
+Esperanto: Mi povas manĝi vitron, ĝi ne damaĝas min.
+Pictish: (NEEDED)
+Breton: (NEEDED)
+Cornish: Mý a yl dybry gwéder hag éf ny wra ow ankenya.
+Welsh: Dw i'n gallu bwyta gwydr, 'dyw e ddim yn gwneud dolur i mi.
+Manx Gaelic: Foddym gee glonney agh cha jean eh gortaghey mee.
+Old Irish (Ogham): ᚛᚛ᚉᚑᚅᚔᚉᚉᚔᚋ ᚔᚈᚔ ᚍᚂᚐᚅᚑ ᚅᚔᚋᚌᚓᚅᚐ᚜
+Old Irish (Latin): Con·iccim ithi nglano. Ním·géna.
+Irish: Is féidir liom gloinne a ithe. Ní dhéanann sí dochar ar bith dom.
+Scottish Gaelic: S urrainn dhomh gloinne ithe; cha ghoirtich i mi.
+Anglo-Saxon (Runes): ᛁᚳ᛫ᛗᚨᚷ᛫ᚷᛚᚨᛋ᛫ᛖᚩᛏᚪᚾ᛫ᚩᚾᛞ᛫ᚻᛁᛏ᛫ᚾᛖ᛫ᚻᛖᚪᚱᛗᛁᚪᚧ᛫ᛗᛖ᛬
+Anglo-Saxon (Latin): Ic mæg glæs eotan ond hit ne hearmiað me.
+Middle English: Ich canne glas eten and hit hirtiþ me nouȝt.
+English: I can eat glass and it doesn't hurt me.
+English (IPA): [aɪ kæn iːt glɑːs ænd ɪt dɐz nɒt hɜːt miː] (Received Pronunciation)
+English (Braille): ⠊⠀⠉⠁⠝⠀⠑⠁⠞⠀⠛⠇⠁⠎⠎⠀⠁⠝⠙⠀⠊⠞⠀⠙⠕⠑⠎⠝⠞⠀⠓⠥⠗⠞⠀⠍⠑
+Lalland Scots / Doric: Ah can eat gless, it disnae hurt us.
+Glaswegian: (NEEDED)
+Gothic: 𐌼𐌰𐌲 𐌲𐌻𐌴𐍃 𐌹̈𐍄𐌰𐌽, 𐌽𐌹 𐌼𐌹𐍃 𐍅𐌿 𐌽𐌳𐌰𐌽 𐌱𐍂𐌹𐌲𐌲𐌹𐌸.
+Old Norse (Runes): ᛖᚴ ᚷᛖᛏ ᛖᛏᛁ ᚧ ᚷᛚᛖᚱ ᛘᚾ ᚦᛖᛋᛋ ᚨᚧ ᚡᛖ ᚱᚧᚨ ᛋᚨᚱ
+Old Norse (Latin): Ek get etið gler án þess að verða sár.
+Norsk / Norwegian Nynorsk: Eg kan eta glas utan å skada meg.
+Norsk / Norwegian Bokmål: Jeg kan spise glass uten å skade meg.
+Føroyskt / Faroese: (NEEDED)
+Íslenska / Icelandic: Ég get etið gler án þess að meiða mig.
+Svenska / Swedish: Jag kan äta glas utan att skada mig.
+Dansk / Danish: Jeg kan spise glas, det gør ikke ondt på mig.
+Frysk / Frisian: Ik kin glês ite, it docht me net sear.
+Nederlands / Dutch: Ik kan glas eten, het doet mij geen kwaad.
+Afrikaans: Ek kan glas eet, maar dit doen my nie skade nie.
+Lëtzebuergescht / Luxemburgish: Ech kan Glas iessen, daat deet mir nët wei.
+Deutsch / German: Ich kann Glas essen, ohne mir weh zu tun.
+Ruhrdeutsch: Ich kann Glas verkasematuckeln, ohne dattet mich wat jucken tut.
+Lausitzer Mundart ("Lusatian"): Ich koann Gloos assn und doas dudd merr ni wii.
+Odenwälderisch: Iech konn glaasch voschbachteln ohne dass es mir ebbs daun doun dud.
+Sächsisch / Saxon: 'sch kann Glos essn, ohne dass'sch mer wehtue.
+Pfälzisch: Isch konn Glass fresse ohne dasses mer ebbes ausmache dud.
+Schwäbisch / Swabian: I kå Glas frässa, ond des macht mr nix!
+Bayrisch / Bavarian: I koh Glos esa, und es duard ma ned wei.
+Allemannisch: I kaun Gloos essen, es tuat ma ned weh.
+Schwyzerdütsch: Ich chan Glaas ässe, das tuet mir nöd weeh.
+Hungarian: Meg tudom enni az üveget, nem lesz tőle bajom.
+Suomi / Finnish: Voin syödä lasia, se ei vahingoita minua.
+Sami (Northern): Sáhtán borrat lása, dat ii leat bávččas.
+Erzian: Мон ярсан суликадо, ды зыян эйстэнзэ а ули.
+Karelian: (NEEDED)
+Vepsian: (NEEDED)
+Votian: (NEEDED)
+Livonian: (NEEDED)
+Estonian: Ma võin klaasi süüa, see ei tee mulle midagi.
+Latvian: Es varu ēst stiklu, tas man nekaitē.
+Lithuanian: Aš galiu valgyti stiklą ir jis manęs nežeidžia
+Old Prussian: (NEEDED)
+Sorbian (Wendish): (NEEDED)
+Czech: Mohu jíst sklo, neublíží mi.
+Slovak: Môžem jesť sklo. Nezraní ma.
+Polska / Polish: Mogę jeść szkło i mi nie szkodzi.
+Slovenian: Lahko jem steklo, ne da bi mi škodovalo.
+Croatian: Ja mogu jesti staklo i ne boli me.
+Serbian (Latin): Mogu jesti staklo a da mi ne škodi.
+Serbian (Cyrillic): Могу јести стакло а да ми не шкоди.
+Macedonian: Можам да јадам стакло, а не ме штета.
+Russian: Я могу есть стекло, оно мне не вредит.
+Belarusian (Cyrillic): Я магу есці шкло, яно мне не шкодзіць.
+Belarusian (Lacinka): Ja mahu jeści škło, jano mne ne škodzić.
+Ukrainian: Я можу їсти шкло, й воно мені не пошкодить.
+Bulgarian: Мога да ям стъкло, то не ми вреди.
+Georgian: მინას ვჭამ და არა მტკივა.
+Armenian: Կրնամ ապակի ուտել և ինծի անհանգիստ չըներ։
+Albanian: Unë mund të ha qelq dhe nuk më gjen gjë.
+Turkish: Cam yiyebilirim, bana zararı dokunmaz.
+Turkish (Ottoman): جام ييه بلورم بڭا ضررى طوقونمز
+Bangla / Bengali: আমি কাঁচ খেতে পারি, তাতে আমার কোনো ক্ষতি হয় না।
+Marathi: मी काच खाऊ शकतो, मला ते दुखत नाही.
+Hindi: मैं काँच खा सकता हूँ, मुझे उस से कोई पीडा नहीं होती.
+Tamil: நான் கண்ணாடி சாப்பிடுவேன், அதனால் எனக்கு ஒரு கேடும் வராது.
+Urdu: میں کانچ کھا سکتا ہوں اور مجھے تکلیف نہیں ہوتی ۔
+Pashto: زه شيشه خوړلې شم، هغه ما نه خوږوي
+Farsi / Persian: .من می‌توانم بدونِ احساسِ درد شيشه بخورم
+Arabic: أنا قادر على أكل الزجاج و هذا لا يؤلمني.
+Aramaic: (NEEDED)
+Hebrew: אני יכול לאכול זכוכית וזה לא מזיק לי.
+Yiddish: איך קען עסן גלאָז און עס טוט מיר נישט װײ.
+Judeo-Arabic: (NEEDED)
+Ladino: (NEEDED)
+Gǝʼǝz: (NEEDED)
+Amharic: (NEEDED)
+Twi: Metumi awe tumpan, ɜnyɜ me hwee.
+Hausa (Latin): Inā iya taunar gilāshi kuma in gamā lāfiyā.
+Hausa (Ajami): إِنا إِىَ تَونَر غِلَاشِ كُمَ إِن غَمَا لَافِىَا
+Yoruba: Mo lè je̩ dígí, kò ní pa mí lára.
+(Ki)Swahili: Naweza kula bilauri na sikunyui.
+Malay: Saya boleh makan kaca dan ia tidak mencederakan saya.
+Tagalog: Kaya kong kumain nang bubog at hindi ako masaktan.
+Chamorro: Siña yo' chumocho krestat, ti ha na'lalamen yo'.
+Javanese: Aku isa mangan beling tanpa lara.
+Burmese: (NEEDED)
+Vietnamese (quốc ngữ): Tôi có thể ăn thủy tinh mà không hại gì.
+Vietnamese (nôm): 些 𣎏 世 咹 水 晶 𦓡 空 𣎏 害 咦
+Khmer: (NEEDED)
+Lao: (NEEDED)
+Thai: ฉันกินกระจกได้ แต่มันไม่ทำให้ฉันเจ็บ
+Mongolian: ᠪᠢ ᠰᠢᠯᠢ ᠢᠳᠡᠶᠦ ᠴᠢᠳᠠᠨᠠ ᠂ ᠨᠠᠳᠤᠷ ᠬᠣᠤᠷᠠᠳᠠᠢ ᠪᠢᠰᠢ
+Mongolian (Cyrillic): Би шил идэй чадна, надад хортой биш
+Dzongkha: (NEEDED)
+Nepali: (NEEDED)
+Tibetan: ཤེལ་སྒོ་ཟ་ནས་ང་ན་གི་མ་རེད།
+Chinese Simplified: 我能吞下玻璃而不伤身体。
+Chinese Traditional: 我能吞下玻璃而不傷身體。
+Taiwanese: Góa ē-tàng chia̍h po-lê, mā bē tio̍h-siong.
+Japanese: 私はガラスを食べられます。それは私を傷つけません。
+Korean: 나는 유리를 먹을 수 있어요. 그래도 아프지 않아요
+Bislama: Mi save kakae glas, hemi no save katem mi.
+Hawaiian: Hiki iaʻu ke ʻai i ke aniani; ʻaʻole nō lā au e ʻeha.
+Marquesan: E koʻana e kai i te karahi, mea ʻā, ʻaʻe hauhau.
+Chinook Jargon: Naika məkmək kakshət labutay, pi weyk ukuk munk-sik nay.
+Navajo: Tsésǫʼ yishą́ągo bííníshghah dóó doo shił neezgai da.
+Cherokee (and Cree, Ojibwa, Inuktitut, and other Native American languages): (NEEDED)
+Garifuna: (NEEDED)
+Gullah: (NEEDED)
+Lojban: mi kakne le nu citka le blaci .iku'i le se go'i na xrani mi
+Nórdicg: Ljœr ye caudran créneþ ý jor cẃran.
diff --git a/utils/HELLO.txt b/utils/HELLO.txt
new file mode 100644
index 00000000..21acb941
--- /dev/null
+++ b/utils/HELLO.txt
@@ -0,0 +1,54 @@
+This is Pango (Παν語)
+
+This is a list of ways to say hello in various languages.
+Its purpose is to illustrate a number of scripts.
+
+---------------------------------------------------------
+Arabic السَّلام عليكُم
+Bengali (বাঙ্লা) ষাগতোম
+Burmese မ္ရန္မာ
+Cherokee (ᏣᎳᎩ) ᎣᏏᏲ
+Czech (česky) Dobrý den
+Danish (Dansk) Hej, Goddag
+English Hello
+Esperanto Saluton
+Estonian Tere, Tervist
+FORTRAN PROGRAM
+Finnish (Suomi) Hei
+French (Français) Bonjour, Salut
+German (Deutsch Nord) Guten Tag
+German (Deutsch Süd) Grüß Gott
+Georgian (ქართველი) გამარჯობა
+Gujarati ગુજરાતિ
+Greek (Ελληνικά) Γειά σας
+Hebrew שלום
+Hindi नमस्ते, नमस्कार।
+Italiano Ciao, Buon giorno
+IPA English (ɪŋglɪʃ) hɛləʊ
+Lao ສບາຍດ
+Maltese Ċaw, Saħħa
+Nederlands, Vlaams Hallo, Dag
+Norwegian (Norsk) Hei, God dag
+Punjabi ਪੁਂਜਾਬਿ
+Polish Dzień dobry, Hej
+Russian (Русский) Здравствуйте!
+Sinhala (සිංහල) ආයුබෝවන්
+Slovak Dobrý deň
+Spanish (Español) ¡Hola!
+Swedish (Svenska) Hej, Goddag
+Thai (ภาษาไทย) สวัสดีครับ, สวัสดีค่ะ
+Tamil (தமிழ்) வணக்கம்
+Turkish (Türkçe) Merhaba
+Vietnamese (Tiếng Việt) Xin Chào
+Yiddish (ײַדישע)‎ דאָס הײַזעלע
+
+Japanese (日本語) こんにちは, コンニチハ
+Chinese (中文,普通话,汉语) 你好
+Cantonese (粵語,廣東話) 早晨, 你好
+Korean (한글) 안녕하세요, 안녕하십니까
+
+Difference among chinese characters in GB, JIS, KSC, BIG5:
+ GB -- 元气 开发
+ JIS -- 元気 開発
+ KSC -- 元氣 開發
+ BIG5 -- 元氣 開發
diff --git a/utils/Makefile.am b/utils/Makefile.am
new file mode 100644
index 00000000..b432e50f
--- /dev/null
+++ b/utils/Makefile.am
@@ -0,0 +1,104 @@
+## Process this file with automake to create Makefile.in.
+
+TEST_TEXTS = \
+ test-arabic.txt \
+ test-chinese.txt \
+ test-devanagari.txt \
+ test-feature-tag.markup \
+ test-gurmukhi.txt \
+ test-hebrew.txt \
+ test-ipa.txt \
+ test-justify.txt \
+ test-lao.txt \
+ test-latin.txt \
+ test-long-paragraph.txt \
+ test-mixed.markup \
+ test-mixed.txt \
+ test-mongolian.txt \
+ test-nko.txt \
+ test-opentype-language.markup \
+ test-syriac.txt \
+ test-tamil.txt \
+ test-thai.txt \
+ test-tibetan.txt \
+ HELLO.txt \
+ GLASS.txt
+
+EXTRA_DIST = \
+ $(TEST_TEXTS)
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ $(PANGO_DEBUG_FLAGS) \
+ $(GLIB_CFLAGS) \
+ $(XFT_CFLAGS) \
+ $(CAIRO_CFLAGS) \
+ $(FREETYPE_CFLAGS) \
+ $(FONTCONFIG_CFLAGS) \
+ $(X_CFLAGS)
+
+#########################################################
+
+bin_PROGRAMS = pango-view
+pango_view_SOURCES = \
+ viewer-render.h \
+ viewer-render.c \
+ viewer.h \
+ viewer-main.c \
+ pango-view.c
+pango_view_LDADD = \
+ ../pango/libpango-$(PANGO_API_VERSION).la \
+ $(GLIB_LIBS)
+if HAVE_FREETYPE
+pango_view_SOURCES += \
+ viewer-pangoft2.c
+pango_view_LDADD += \
+ ../pango/libpangoft2-$(PANGO_API_VERSION).la \
+ $(FREETYPE_LIBS) \
+ $(FONTCONFIG_LIBS)
+endif
+if HAVE_XFT
+pango_view_SOURCES += \
+ viewer-pangoxft.c \
+ viewer-x.h \
+ viewer-x.c
+pango_view_LDADD += \
+ ../pango/libpangoft2-$(PANGO_API_VERSION).la \
+ ../pango/libpangoxft-$(PANGO_API_VERSION).la \
+ $(XFT_LIBS)
+endif
+if HAVE_CAIRO
+pango_view_SOURCES += \
+ viewer-cairo.h \
+ viewer-cairo.c \
+ viewer-pangocairo.c
+pango_view_LDADD += \
+ ../pango/libpangocairo-$(PANGO_API_VERSION).la \
+ $(CAIRO_LIBS)
+endif
+
+#########################################################
+
+MAINTAINERCLEANFILES = pango-view.1.in
+EXTRA_DIST += pango-view.1.in
+CLEANFILES = pango-view.1
+
+nodist_man_MANS = pango-view.1
+
+# The indirection through pango-view.1.in is to make parallel build work.
+# See bug 587768.
+$(srcdir)/pango-view.1.in: ../configure.ac $(pango_view_SOURCES)
+ $(AM_V_GEN) $(top_srcdir)/missing --run \
+ help2man --no-info --section=1 \
+ --help-option="--help-all" --output="$@.tmp" \
+ --name 'Pango text viewer' ./pango-view \
+ && mv "$@.tmp" "$@" \
+ || (echo Failed to update pango-view.1, the man page may be outdated >&2; \
+ (test -f "$@" || echo help2man is required to generate this file. >> "$@"));
+pango-view.1: pango-view$(EXEEXT)
+ $(AM_V_GEN) $(MAKE) $(AM_MAKEFLAGS) pango-view.1.in && \
+ cp $(srcdir)/pango-view.1.in $@
+
+#########################################################
+
+-include $(top_srcdir)/git.mk
diff --git a/utils/meson.build b/utils/meson.build
new file mode 100644
index 00000000..a0043fc7
--- /dev/null
+++ b/utils/meson.build
@@ -0,0 +1,58 @@
+pango_view_sources = [
+ 'pango-view.c',
+ 'viewer-main.c',
+ 'viewer-render.c',
+]
+
+pango_view_deps = [
+ pango_deps,
+ libpango_dep,
+]
+
+if build_pangoft2
+ pango_view_sources += 'viewer-pangoft2.c'
+ pango_view_deps += libpangoft2_dep
+endif
+
+if xft_dep.found() and build_pangoft2
+ pango_view_sources += [
+ 'viewer-pangoxft.c',
+ 'viewer-x.c',
+ ]
+ pango_view_deps += [ libpangoft2_dep, libpangoxft_dep, ]
+endif
+
+if cairo_dep.found()
+ pango_view_sources += [
+ 'viewer-cairo.c',
+ 'viewer-pangocairo.c',
+ ]
+ pango_view_deps += libpangocairo_dep
+endif
+
+pango_view = executable('pango-view', pango_view_sources,
+ dependencies: pango_view_deps,
+ include_directories: [ root_inc ],
+ install: true,
+ c_args: [
+ '-DPACKAGE_NAME="@0@"'.format(meson.project_name()),
+ '-DPACKAGE_VERSION="@0@"'.format(meson.project_version()),
+ ])
+
+help2man = find_program('help2man', required: false)
+if help2man.found()
+ help2man_opts = [
+ '--no-info',
+ '--section=1',
+ '--help-option=--help-all',
+ '--name=Pango text viewer',
+ ]
+
+ custom_target('pango-view.1',
+ output: 'pango-view.1',
+ command: [
+ help2man, help2man_opts, '--output=@OUTPUT@', pango_view
+ ],
+ install: true,
+ install_dir: join_paths(pango_datadir, 'man/man1'))
+endif
diff --git a/utils/pango-view.c b/utils/pango-view.c
new file mode 100644
index 00000000..fec00be9
--- /dev/null
+++ b/utils/pango-view.c
@@ -0,0 +1,23 @@
+#include "config.h"
+#include "viewer.h"
+
+extern const PangoViewer pangocairo_viewer;
+extern const PangoViewer pangoxft_viewer;
+extern const PangoViewer pangoft2_viewer;
+extern const PangoViewer pangox_viewer;
+
+const PangoViewer *viewers[] = {
+#ifdef HAVE_CAIRO
+ &pangocairo_viewer,
+#endif
+#ifdef HAVE_XFT
+ &pangoxft_viewer,
+#endif
+#ifdef HAVE_FREETYPE
+ &pangoft2_viewer,
+#endif
+#ifdef HAVE_X
+ &pangox_viewer,
+#endif
+ NULL
+};
diff --git a/utils/test-arabic.txt b/utils/test-arabic.txt
new file mode 100644
index 00000000..b75f9a6c
--- /dev/null
+++ b/utils/test-arabic.txt
@@ -0,0 +1,7 @@
+بِسْمِ ٱللّٰهِ ٱلرَّحْمٰنِ ٱلرَّحِيمِ
+اَلْحَمْدُ لِلّٰهِ رَبِّ الْعَالَمِينَ
+اَلرَّحْمٰنِ الرَّحِيمِ
+مَالِكِ يَوْمِ الدِّينِ
+اِيَّاكَ نَعْبُدُ وَ اِيَّاكَ نَسْتَعِينُ
+اِهْدِنَا الصِّرَاطَ الْمُسْتَقِيمَ
+صِرَاطَ الَّذِينَ اَنْعَمْتَ عَلَيهِمْ غَيْرِ الْمَغْضُوبِ عَلَيْهِم وَ لَا الضَّاۤلِّينَ
diff --git a/utils/test-chinese.txt b/utils/test-chinese.txt
new file mode 100644
index 00000000..bddc8234
--- /dev/null
+++ b/utils/test-chinese.txt
@@ -0,0 +1,21 @@
+你好,这是中文竖排测试。
+欢迎来到中国北京。
+白日依山尽,
+黄河入海流。
+欲穷千里目,
+更上一层楼。
+谢谢!
+
+
+《施氏吃獅子記》
+
+有一位住在石室裏的詩人叫施氏,愛吃獅子,決心要吃十隻獅子。
+他常常去市場看獅子。
+十點鐘,剛好有十隻獅子到了市場。
+那時候,剛好施氏也到了市場。
+他看見那十隻獅子,便放箭,把那十隻獅子殺死了。
+他拾起那十隻獅子的屍體,帶到石室。
+石室濕了水,施氏叫侍從把石室擦乾。
+石室擦乾了,他才試試吃那十隻獅子。
+吃的時候,才發現那十隻獅子,原來是十隻石頭的獅子屍體。
+試試解釋這件事吧。
diff --git a/utils/test-devanagari.txt b/utils/test-devanagari.txt
new file mode 100644
index 00000000..9cfccc56
--- /dev/null
+++ b/utils/test-devanagari.txt
@@ -0,0 +1,9 @@
+कि KI
+किकि KIKI
+क्कि KKI
+क्क्कि KKKI
+क््कि KKI (sp)
+क़्कि KKI (sp)
+क््क्कि KKKI (sp)
+क््क््कि KKKI (sp)
+क़्क़्कि KKKI (sp)
diff --git a/utils/test-feature-tag.markup b/utils/test-feature-tag.markup
new file mode 100644
index 00000000..c92fc646
--- /dev/null
+++ b/utils/test-feature-tag.markup
@@ -0,0 +1,130 @@
+<span font_desc="IPAexGothic 8">
+ASCII:
+0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
+Full Widths:
+<span font_features="fwid">
+0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
+</span>
+Half Widths:
+やさしいカレー
+<span font_features="hwid 1">やさしいカレー</span>
+
+Standard Ligatures:
+<span font_desc="FreeSans 8">
+<span font_features="liga off">ffl fi</span>
+ffl fi
+</span>
+
+Discretionary Ligatures:
+株式会社
+<span font_features="dlig on">株式会社</span>
+
+Discretionary Ligatures (+ Vertical Alternates)
+株式会社
+<span font_features="vert on, dlig on">株式会社</span>
+
+Vertical Alternates:
+<span font_features="vert 1">私の名前は中野です。</span>
+
+Fractions:
+<span font_desc="Linux Libertine O 8">
+1/1000
+<span font_features="frac">1/1000</span>
+</span>
+
+Traditional Forms:
+亜
+<span font_features="trad">亜</span>
+
+JIS X 0213:2000
+<span font_features="jp90">
+逢芦飴溢茨鰯淫迂厩噂餌襖迦牙廻恢晦蟹葛鞄釜翰翫徽
+祇汲灸笈卿饗僅喰櫛屑粂祁隙倦捲牽鍵諺巷梗膏鵠甑叉
+榊薩鯖錆鮫餐杓灼酋楯薯藷哨鞘杖蝕訊逗摺撰煎煽穿箭
+詮噌遡揃遜腿蛸辿樽歎註瀦捗槌鎚辻挺鄭擢溺兎堵屠賭
+瀞遁謎灘楢禰牌這秤駁箸叛挽誹樋稗逼謬豹廟瀕斧蔽瞥
+蔑篇娩鞭庖蓬鱒迄儲餅籾爺鑓愈猷漣煉簾榔屢冤叟咬嘲
+囀徘扁棘橙狡甕甦疼祟竈筵篝腱艘芒虔蜃蠅訝靄靱騙鴉
+</span>
+
+JIS X 0213:2004
+<span font_features="jp04">
+逢芦飴溢茨鰯淫迂厩噂餌襖迦牙廻恢晦蟹葛鞄釜翰翫徽
+祇汲灸笈卿饗僅喰櫛屑粂祁隙倦捲牽鍵諺巷梗膏鵠甑叉
+榊薩鯖錆鮫餐杓灼酋楯薯藷哨鞘杖蝕訊逗摺撰煎煽穿箭
+詮噌遡揃遜腿蛸辿樽歎註瀦捗槌鎚辻挺鄭擢溺兎堵屠賭
+瀞遁謎灘楢禰牌這秤駁箸叛挽誹樋稗逼謬豹廟瀕斧蔽瞥
+蔑篇娩鞭庖蓬鱒迄儲餅籾爺鑓愈猷漣煉簾榔屢冤叟咬嘲
+囀徘扁棘橙狡甕甦疼祟竈筵篝腱艘芒虔蜃蠅訝靄靱騙鴉
+</span>
+
+Alternate Annotation Forms:
+CR+12345一二三月火水木金土日株有社名
+<span font_features="nalt">CR+12345一二三月火水木金土日株有社名</span>
+
+</span>
+<span font_desc="IPAexGothic 8">
+ASCII:
+0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
+Full Widths:
+<span font_features="fwid">
+0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
+</span>
+Half Widths:
+やさしいカレー
+<span font_features="hwid 1">やさしいカレー</span>
+
+Standard Ligatures:
+<span font_desc="FreeSans 8">
+<span font_features="liga off">ffl fi</span>
+ffl fi
+</span>
+
+Discretionary Ligatures:
+株式会社
+<span font_features="dlig on">株式会社</span>
+
+Discretionary Ligatures (+ Vertical Alternates)
+株式会社
+<span font_features="vert on, dlig on">株式会社</span>
+
+Vertical Alternates:
+<span font_features="vert 1">私の名前は中野です。</span>
+
+Fractions:
+<span font_desc="Linux Libertine O 8">
+1/1000
+<span font_features="frac">1/1000</span>
+</span>
+
+Traditional Forms:
+亜
+<span font_features="trad">亜</span>
+
+JIS X 0213:2000
+<span font_features="jp90">
+逢芦飴溢茨鰯淫迂厩噂餌襖迦牙廻恢晦蟹葛鞄釜翰翫徽
+祇汲灸笈卿饗僅喰櫛屑粂祁隙倦捲牽鍵諺巷梗膏鵠甑叉
+榊薩鯖錆鮫餐杓灼酋楯薯藷哨鞘杖蝕訊逗摺撰煎煽穿箭
+詮噌遡揃遜腿蛸辿樽歎註瀦捗槌鎚辻挺鄭擢溺兎堵屠賭
+瀞遁謎灘楢禰牌這秤駁箸叛挽誹樋稗逼謬豹廟瀕斧蔽瞥
+蔑篇娩鞭庖蓬鱒迄儲餅籾爺鑓愈猷漣煉簾榔屢冤叟咬嘲
+囀徘扁棘橙狡甕甦疼祟竈筵篝腱艘芒虔蜃蠅訝靄靱騙鴉
+</span>
+
+JIS X 0213:2004
+<span font_features="jp04">
+逢芦飴溢茨鰯淫迂厩噂餌襖迦牙廻恢晦蟹葛鞄釜翰翫徽
+祇汲灸笈卿饗僅喰櫛屑粂祁隙倦捲牽鍵諺巷梗膏鵠甑叉
+榊薩鯖錆鮫餐杓灼酋楯薯藷哨鞘杖蝕訊逗摺撰煎煽穿箭
+詮噌遡揃遜腿蛸辿樽歎註瀦捗槌鎚辻挺鄭擢溺兎堵屠賭
+瀞遁謎灘楢禰牌這秤駁箸叛挽誹樋稗逼謬豹廟瀕斧蔽瞥
+蔑篇娩鞭庖蓬鱒迄儲餅籾爺鑓愈猷漣煉簾榔屢冤叟咬嘲
+囀徘扁棘橙狡甕甦疼祟竈筵篝腱艘芒虔蜃蠅訝靄靱騙鴉
+</span>
+
+Alternate Annotation Forms:
+CR+12345一二三月火水木金土日株有社名
+<span font_features="nalt">CR+12345一二三月火水木金土日株有社名</span>
+
+</span>
diff --git a/utils/test-font-variations.markup b/utils/test-font-variations.markup
new file mode 100644
index 00000000..ae277e35
--- /dev/null
+++ b/utils/test-font-variations.markup
@@ -0,0 +1,9 @@
+Rendering Text using weight variations.
+This works with fonts that have OpenType
+font variations.
+<span font_desc="@wght=700">Weight: 700</span>
+<span font_desc="@wght=600">Weight: 600</span>
+<span font_desc="@wght=300">Weight: 300</span>
+<span font_desc="@wght=500">Weight: 500</span>
+<span font_desc="@wght=400">Weight: 400</span>
+<span font_desc="@wght=2000">Weight: 2000</span>
diff --git a/utils/test-gurmukhi.txt b/utils/test-gurmukhi.txt
new file mode 100644
index 00000000..f3d05920
--- /dev/null
+++ b/utils/test-gurmukhi.txt
@@ -0,0 +1,26 @@
+ਹਰੀ ਸਿੰਘ ਨਲੂਆ
+
+ਹਰੀ ਸਿੰਘ ਨਲੂਆ (ਨਲਵਾ)
+
+
+ਸਿੱਖ ਕੌਮ ਦਾ ਅਣਖੀ ਅਤੇ ਬਹਾਦਰ ਜਰਨੈਲ, ਜਿਸ ਦੇ ਨਾਂ ਤੋਂ ਪਠਾਣੀਆਂ ਆਪਣੇ ਬੱਚਿਆਂ ਨੂੰ ਡਰਾਉਦੀਆਂ ਸਨ ਕਿ ਪੁੱਤ ਸੌਂ ਜਾ ਨਹੀਂ ਤਾਂ ਨਲੂਆ ਆ ਜਾਵੇਗਾ।
+
+
+ਵਿਸ਼ਾ-ਸੂਚੀ
+[ਛੁਪਾਓ]
+
+ * ੧ ਜਨਮ ਅਤੇ ਸਿਖਲਾਈ
+ * ੨ ਨਲੂਆ
+ * ੩ ਖਾਲਸਾ ਫੌਜ
+ * ੪ ਕਸ਼ਮੀਰ
+ * ੫ ਜੰਗ ਜਮਰੌਦ
+ * ੬ ਜਾਗੀਰ ਵਾਪਸੀ
+
+ ਜਨਮ ਅਤੇ ਸਿਖਲਾਈ
+
+ਸਰਦਾਰ ਹਰੀ ਸਿੰਘ ਦਾ ਜਨਮ ਗੁੱਜਰਾਵਾਲੇ ਵਿਖੇ ਹੋਇਆ। ਪਿਤਾ ਦੀ ਮੌਤ ਨਿੱਕੀ ਉਮਰ ਵਿੱਚ ਹੋਣ ਉਪਰੰਤ, ਉਹਨਾਂ ਦਾ ਪਾਲਣ ਪੋਸ਼ਣ ਉਹਨਾਂ ਦੇ ਨਾਨਕੇ ਪਰਿਵਾਰ ਵਿੱਚ ਹੋਇਆ।
+
+ਸਰਦਾਰ ਹਰੀ ਸਿੰਘ ਨਲੂਆ ਦੇ ਪਰਿਵਾਰ ਵਿੱਚ ਦੋ ਪਤਨੀਆਂ, ਜਿੰਨ੍ਹਾਂ ਦਾ ਜ਼ੋਰਾਵਰ ਸਿੰਘ ਤੇ ਗੁਰਦਿੱਤ ਸਿੰਘ, ਅਤੇ ਅਰਜਨ ਸਿੰਘ ਤੇ ਪੰਜਾਬ ਸਿੰਘ ਨਾਂ ਦੇ ਚਾਰ ਪੁੱਤਰ ਸਨ। ਚਾਰੇ ਪੁੱਤਰ ਆਪਣੇ ਪਿਓ ਵਾਂਗ ਬਹਾਦਰ ਨਹੀਂ ਸਨ।
+ਇਹਨਾਂ ਕਰਕੇ ਮਹਾਰਾਜਾ ਰਣਜੀਤ ਸਿੰਘ ਨੇ ਅੰਤ ਵਿੱਚ ਸਰਦਾਰ ਹਰੀ ਸਿੰਘ ਦੀ ਸਾਰੀ ਜਾਗੀਰ ਨੂੰ ਵਾਪਸ ਲੈ ਲਈ ਆਪਣੇ ਸਰਦਾਰ ਵਿੱਚ ਵੰਡ ਦਿੱਤੀ।
+ਸਰਦਾਰ ਹਰੀ ਸਿੰਘ ਦੇ ਬਾਅਦ ਇਹਨਾਂ ਕੋਲ 40,000 ਹਜ਼ਾਰ ਦੀ ਸਲਾਨਾ ਜਾਗੀਰ ਅਤੇ 60 ਤੋਂ 70 ਸਵਾਰ ਰਹੇ।
+
diff --git a/utils/test-hebrew.txt b/utils/test-hebrew.txt
new file mode 100644
index 00000000..15467d87
--- /dev/null
+++ b/utils/test-hebrew.txt
@@ -0,0 +1,13 @@
+בְּרֵאשִׁ֖ית בָּרָ֣א אֱלֹהִ֑ים אֵ֥ת הַשָּׁמַ֖יִם וְאֵ֥ת הָאָֽרֶץ׃
+וְהָאָ֗רֶץ הָֽיְתָ֥ה תֹ֨הוּ֙ וָבֹ֔הוּ וְחֹ֖שֶׁךְ עַל־פְּנֵ֣י תְה֑וֹם וְר֣וּחַ אֱלֹהִ֔ים מְרַחֶ֖פֶת עַל־פְּנֵ֥י הַמָּֽיִם׃
+וַיֹּ֥אמֶר אֱלֹהִ֖ים יְהִ֣י א֑וֹר וַֽיְהִי־אֽוֹר׃
+וַיַּ֧רְא אֱלֹהִ֛ים אֶת־הָא֖וֹר כִּי־ט֑וֹב וַיַּבְדֵּ֣ל אֱלֹהִ֔ים בֵּ֥ין הָא֖וֹר וּבֵ֥ין הַחֹֽשֶׁךְ׃
+וַיִּקְרָ֨א אֱלֹהִ֤ים ׀ לָאוֹר֙ י֔וֹם וְלַחֹ֖שֶׁךְ קָ֣רָא לָ֑יְלָה וַֽיְהִי־עֶ֥רֶב וַֽיְהִי־בֹ֖קֶר י֥וֹם אֶחָֽד׃
+וַיֹּ֣אמֶר אֱלֹהִ֔ים יְהִ֥י רָקִ֖יעַ בְּת֣וֹךְ הַמָּ֑יִם וִיהִ֣י מַבְדִּ֔יל בֵּ֥ין מַ֖יִם לָמָֽיִם׃
+וַיַּ֣עַשׂ אֱלֹהִים֮ אֶת־הָֽרָקִיעַ֒ וַיַּבְדֵּ֗ל בֵּ֤ין הַמַּ֨יִם֙ אֲשֶׁר֙ מִתַּ֣חַת לָֽרָקִ֔יעַ וּבֵ֣ין הַמַּ֔יִם אֲשֶׁ֖ר מֵעַ֣ל לָֽרָקִ֑יעַ וַֽיְהִי־כֵֽן׃
+וַיִּקְרָ֧א אֱלֹהִ֛ים לָֽרָקִ֖יעַ שָׁמָ֑יִם וַֽיְהִי־עֶ֥רֶב וַֽיְהִי־בֹ֖קֶר י֥וֹם שֵׁנִֽי׃
+וַיֹּ֣אמֶר אֱלֹהִ֗ים יִקָּו֨וּ הַמַּ֜יִם מִתַּ֤חַת הַשָּׁמַ֨יִם֙ אֶל־מָק֣וֹם אֶחָ֔ד וְתֵֽרָאֶ֖ה הַיַּבָּשָׁ֑ה וַֽיְהִי־כֵֽן׃
+וַיִּקְרָ֨א אֱלֹהִ֤ים ׀ לַיַּבָּשָׁה֙ אֶ֔רֶץ וּלְמִקְוֵ֥ה הַמַּ֖יִם קָרָ֣א יַמִּ֑ים וַיַּ֥רְא אֱלֹהִ֖ים כִּי־טֽוֹב׃
+וַיֹּ֣אמֶר אֱלֹהִ֗ים תַּֽדְשֵׁ֤א הָאָ֨רֶץ֙ דֶּ֗שֶׁא עֵ֚שֶׂב מַזְרִ֣יעַ זֶ֔רַע עֵ֣ץ פְּרִ֞י עֹ֤שֶׂה פְּרִי֙ לְמִינ֔וֹ אֲשֶׁ֥ר זַרְעוֹ־ב֖וֹ עַל־הָאָ֑רֶץ וַֽיְהִי־כֵֽן׃
+וַתּוֹצֵ֨א הָאָ֜רֶץ דֶּ֠שֶׁא עֵ֣שֶׂב מַזְרִ֤יעַ זֶ֨רַע֙ לְמִינֵ֔הוּ וְעֵ֧ץ עֹֽשֶׂה־פְּרִ֛י אֲשֶׁ֥ר זַרְעוֹ־ב֖וֹ לְמִינֵ֑הוּ וַיַּ֥רְא אֱלֹהִ֖ים כִּי־טֽוֹב׃
+וַֽיְהִי־עֶ֥רֶב וַֽיְהִי־בֹ֖קֶר י֥וֹם שְׁלִישִֽׁי׃
diff --git a/utils/test-ipa.txt b/utils/test-ipa.txt
new file mode 100644
index 00000000..2fdd5e2c
--- /dev/null
+++ b/utils/test-ipa.txt
@@ -0,0 +1,8 @@
+n̩ a̩ m̩ w̩ i̩ l̩ j̩̩ ɳ̩ ŋ̩
+n̽ a̽ m̽ w̽ i̽ l̽ j̽ ɳ̽ ŋ̽
+ff fi fl e˥˩ i˦˨˥
+ä̃́ ɛ̃̈̀ ɩ̂́ ɔ̃̂ ʉ̄̈ ɠ̪̥̈
+ŋ̥ n̥ n̪̰
+k͡p m͡i l͡w l͡i m͡w
+
+
diff --git a/utils/test-justify.txt b/utils/test-justify.txt
new file mode 100644
index 00000000..754ed28c
--- /dev/null
+++ b/utils/test-justify.txt
@@ -0,0 +1,5 @@
+<span letter_spacing="2048">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc sit amet nulla. Cras elit. Fusce nibh orci, vehicula ut, congue ut, auctor quis, turpis.</span>
+
+Fusce ullamcorper porttitor quam. Nullam at nulla sit amet neque rutrum porttitor. Quisque mattis blandit velit. Aenean nec risus sed libero rhoncus suscipit. Cras dapibus.
+
+قصابه با بابام رفیقه. پشت خونمون یه دباغیه. اینقده بچه گوسفند توشه! خونه‌مون ساس داره. ساس کوچیک و سیاس. هر جا بزنه جاش باد می‌کنه. وقتی داره از دیوار اتاق می‌ره بالا، نمی‌تونه خودشو نگه داره، می‌افته رو تن ما، می‌گیره خونمونو می‌مکه. یه دفعه همه اثاث مثاثامونو ریختیم بیرون، یه عالمه دوا خریدیم زدیم همه جا: به رختخوابا،‌ زیر زیلو، سوراخ سنبه‌ها. ولی ساسها بیشتر شدن، کمتر نشدن. بابام توی حموم کار می‌کنه.
diff --git a/utils/test-lao.txt b/utils/test-lao.txt
new file mode 100644
index 00000000..012025e4
--- /dev/null
+++ b/utils/test-lao.txt
@@ -0,0 +1,2 @@
+ເກັບພາບງາມໆມາຝາກໃຫ້ແຟນໆໄດ້ເຫັນເຕັມໆຕາທີ່ມະຫາສານ.ຄອມ
+ ປະຈຸບັນ, ຕ້ອງຍອມຮັບກັນວ່າ ກະແສຕອບຮັບແນວເພງສະຕິງກຳລັງມາແຮງອີ່ຫລີ, ບາງບົດເພງນອກຈາກຈະເປັນທີ່ນິຍົມໃນບ້ານເຮົາ ເມືອງເຮົາແລ້ວ ຍັງຂ້າມຝັ່ງໄປດັງຢູ່ບ້ານເພີ່ນອີກ. ບໍ່ແມ່ນຕັ້ງເວົ້າ ເມື່ອກ່ອນນັກທ່ອງທ່ຽວຊາວຕ່າງຊາດທີ່ເຂົ້າມາທ່ຽວບ້ານເຮົາເພິ່ນມັກຖາມວ່າ ເປັນຫຍັງຄົນລາວຈຶ່ງບໍ່ມັກເປິດເພງລາວໂດຍສະເພາະໃນຮ້ານກິນດື່ມ, ຮ້ານອາຫານ ຫລື ແມ່ນແຕ່ຕາມບ້ານເຮືອນ ແລະ ຕະຫລາດ, ແຕ່ດຽວນີ້ຂໍປະທານໂທດ ເພິ່ນຫັນມາເປີດເພງລາວກັນແລ້ວ ເນື່ອງຈາກສັງຄົມກໍ່ຍອມຮັບ, ບົດເພງກໍ່ມ່ວນ ເນື້ອຫາກໍ່ຖືກໃຈແຖມຍັງເປັນການສົ່ງເສີມໃຫ້ສິລະປິນລາວ ແລະ ຜູ້ຢູ່ເບື້ອງຫລັງມີກຳລັງໃຈພັດທະນາວຽກ ແລະ ຜົນງານຂອງຕົນອີກ... ອີກດ້ານໜື່ງນອກຈານສິລະປິນຈະຕ້ອງບຸກບືນ ເພື່ອໃຫ້ຕົວເອງໄດ້ເຂົ້າມາຢູ່ໃນວົງການແລ້ວ, ຝ່າຍເຈົ້າຂອງຄ້າຍເພງກໍ່ພະຍາຍາມເຟັ້ນຫານັກຮ້ອງສຽງດີ ມີແວວດັງເຂົ້າມາເປັນສະມາຊິກຂອງຄ້າຍເພງຕົນເອງເຊັ່ນກັນ.
diff --git a/utils/test-latin.txt b/utils/test-latin.txt
new file mode 100644
index 00000000..31fe90bd
--- /dev/null
+++ b/utils/test-latin.txt
@@ -0,0 +1,9 @@
+'Twas brillig, and the slithy toves
+Did gyre and gimble in the wabe;
+All mimsy were the borogoves,
+And the mome raths outgrabe.
+
+'Beware the Jabberwock, my son!
+The jaws that bite, the claws that catch!
+Beware the Jubjub bird, and shun
+The frumious Bandersnatch!'
diff --git a/utils/test-long-paragraph.txt b/utils/test-long-paragraph.txt
new file mode 100644
index 00000000..95c795b5
--- /dev/null
+++ b/utils/test-long-paragraph.txt
@@ -0,0 +1,18 @@
+از طرف من و بچه‌ها برای
+شادان حکمت
+
+آقا باور کن، آقا!
+
+-دل من و این تلخی بی‌نهایت
+ سر چشمه‌اش کجاست؟
+- آب دریاها
+ سخت تلخ است آقا!
+ فدریکو گارسیا لورکا
+
+
+
+
+
+
+
+من اسمم کاظمه. ما توی یه کوچه بن بست خونه داریم. کوچه‌مون خاکیه. اونوقت خیلی پایئن تر از خونه ما - زیاد پایین نه - اینور می‌پیچی یه نونواس. از اونجا صاف می‌ریم اینجا. یه خیابونه اینجا. اونوقت خیلی پایین‌ترش یه حمومه. بعداً یه بقالی هم دم خونمونه. یه خرده انور خرابه، یه قصابیه. قصابه با بابام رفیقه. پشت خونمون یه دباغیه. اینقده بچه گوسفند توشه! خونه‌مون ساس داره. ساس کوچیک و سیاس. هر جا بزنه جاش باد می‌کنه. وقتی داره از دیوار اتاق می‌ره بالا، نمی‌تونه خودشو نگه داره، می‌افته رو تن ما، می‌گیره خونمونو می‌مکه. یه دفعه همه اثاث مثاثامونو ریختیم بیرون، یه عالمه دوا خریدیم زدیم همه جا: به رختخوابا،‌ زیر زیلو، سوراخ سنبه‌ها. ولی ساسها بیشتر شدن، کمتر نشدن. بابام توی حموم کار می‌کنه. دوتا برادر داریم، یه خواهر: من و مصطفی و زهرا کوچولو. بابا وقتی داره شب می‌شه برمی‌گرده خونه. همیشه استخوناش درد می‌کنه. سر هیچی به هیچی می‌گیره می‌زنه‌مون، بازهم طلبکاره. مثلاً وسط سال، صبح ساعت شیش می‌آد می‌گه، «پاشو برو سیگار بفروش، پول دربیار لباس بخر!» من هم می‌گم: «لباس می‌خوام چی‌ کار؟» اون هم می‌گیره با کمربند حالمونو جا می‌آره. باز خوبه سه ماه تعطیلی خودمون می‌ریم کار می‌کنیم. یه کارخونه هست. می‌ریم اونجا قابلمه درست می‌کنیم، کاسه درست می‌کنیم، عصر که شونصد تا کاسه درست کردیم، دستگارو تمیز می‌کنیم برمی‌گردیم خونه. پارسال هفته‌ای پنجاه تومن مزد می‌دادن. امسال دیگه خدا می‌دونه. با همه این حرفا، بمیریم بهتره آقا! هر روز هر روز کتک. بابام دیشب بیخودی مصطفی رو گرفت زد. گرفت زدش گفت: «چرا وقتی می‌ری دست به آب، سر پا می‌شاشی؟ بشی بشاش!» مصطفی‌مون هیچی حالیش نمی‌شه. قد زهرامون بوده که از بالا پشت بوم افتاده، رگ کله‌اش تکون خرده. حالا سیزده سالشه. نه چارده،‌ چارده سالشه. داداش بزرگ‌ مونه. الان مدرسه عقب افتاده‌ها درس می‌خونه. آب، بابا، بار میخونه یاد بگیره، بیاد جلو. دو سه کلمه بلده حرف بزنه ولی چیزه... نمیتونه قشنگ حرف بزنه. بابام می‌خواد از مدرسه ورش داره، بذاره یه جا که کار یاد بگیره. بابا زهرا را از همه بیشتر می‌خواد. اون هم هر کاری دلش بخواد می‌کنه. هرچی می‌گیم گوش نمی‌کنه، می‌ره تو جوب محل کثافت‌کاری می‌کنه. اون روزی حواسم نبود، رفت یه مشت دیگ مونده سر کوچه بود ورداشت خورد. شب دلش درد گرفت نزدیک بود بمیره. اونوقت بابام اومد گرفت منو با شیلنگ کشت. آقا مگه شهر هرته؟ خر کتک می‌خوره. دیگه چرا ما رو می‌زنن؟ برن به خر بزنن! آخه من که نمی‌تونم همه‌ش مواظب زهرا باشم. راستی یه صاحب حیاط داریم، خیلی بد اخلاقه آقا! اسمش عباس آقاس. صبح می‌ره ظهر می‌آد. سپور شهرداریه. بیست و چار ساعت می‌آد بند میکنه به ما، میگه: «آب زیاد مصرف نکنین، چاه پر میشه.» زهرامون که گاهی گریه می‌کنه، دادش بلند می‌شه می‌گه: «صدای این تخم‌سگو خفه کنین!» اونوقت که مادرمون زنده بود، یه دفعه می‌خواست از دست عباس آقا نفت بریزه سرش، خودشو آتیش بزنه. عباس آقا اصلاً رحم حالیش نمی‌شه؛ پسر سیزده ساله‌شو گرفته از خونه انداخته بیرون. اون هم رفته توی کوچه‌ پس ‌کوچه‌‌ها ول شده. حالا خدا می‌دونه کجاس، چه کار می‌کنه،‌ از کجا می‌آره می‌خوره. بچه‌ها می‌گن: «شب‌ها می‌ره توی پارک‌ها پیش سگها می‌خوابه.» که رفته دهات خونهٔ باباش، می‌گه دیگه نمی‌آم تهران. آقا، ما هم دلمون می‌خواد میرفتیم دهمون با گوسفندها بازی می‌کردیم؛ با بابا بزرگ‌مون می‌رفتیم دشت بز می‌چروندیم،‌ بادوم پاک می‌کردیم، انگور می‌چیدیم. دهمون ولی خیلی دوره آخه! زن عباس آقا حق داره، آقا! محله‌مون خیلی بده. هر روز اونجا دعواس، دعوا، چاقو کشی. توی خرابه هم پر معتاده، بگی دوهزار تا هم بیشتر. می‌رن اونجا قمار می‌کنن، شیره می‌کشن، آمپول می‌زنن تو رگشون. ماهم از ترس معتادها جرأت نمی‌کنیم از خونه بریم کوچه، یه ذره بازی کنیم. از کمیته‌م نمی‌ترسن، میگیرن بچه‌های مردمو می‌دزدن، میبرن توی کوره‌ها،‌ توی دلاشون چیز قایم می‌کنن؛ هروئین قایم میکنن. یه امیر ریزه هست تریاکیه، اون روزی اومد خرم کنه، گفت: «بیا سوار ماشین بشیم، بریم یه جائی.» من هم از ترسم خر نشدم. یه چیز خنده دار بگم بخندی، آقا: اینورمون یه همسایه داریم، اسمش ربابه. انوقت توپ،‌ لنگه کفش، تنکه، هرچی بیفته خونشون،‌ شوهرش ور می‌داره می‌اندازه توی آب انبارشون. هروقت هم کوچه شلوغ بشه، شوهر رباب می‌آد بیرون می‌گه: «واق، عو!» اون هم مث مصطفی‌ ما لقوه‌ایه‌؛ دستش می‌لزره، همه جاش می‌لرزه. اون روز اومد دم دکون، رفت اونور جوب نشت. این یکی همسایه‌مون رفت یه کتاب دربارهٔ خدا و فرشته‌ها آورد براش خوند. رباب خانم خودش خونه یه اعیونه کار می‌کنه؛ چیزاشونو می‌شوره، باغ‌شونو آب می‌ده؛ کلفتی می‌کنه. بعد همه‌ش می‌آد پز اربابشو می‌ده. الان دیگه همه اهل محل می‌دونن باغ خونهٔ ارباب رباب خانوم اندازه پارک شهره. استخرش از مال پارک شهر هم گنده‌تره. هروقت هم که ارباب می‌خواد‌ آبتنی کنه،‌ اول یه قطره دوای مخصوص هست، می‌ریزه توی استخر که آب‌شو می‌کنه مث اشک چشم. بعد می‌ره زیر دوش، با عطر و گلاب خودشو می‌شوره. بعد می‌پره توی استخر، می‌گیره شوخی شوخی آب می‌پاشه به رباب خانوم. زن اربابش هم خارجیه. مال همون کشوریه که شیش ماه شبه، شیش ماه روز. رباب یه چاخان‌هایی می‌کنه که کلهٔ آدم سوت می‌کشه! می‌گه ارباب یه سگ پشمالو داره،‌ اسمش مونیکاس. قسم می‌خوره می‌گه مونیکا غذاشو با کارد و چنگال می‌خوره. اللَه اکبر به این دروغ. یه پیرزنه هم هست سر کوچمونه. با خودش تنهایی زندگی می‌کنه. اسمش ننه غلامه. هشتاد نود سالشه ولی خجالت نمی‌کشه،‌ از امریکا خوشش می‌آد. همه ازش می‌ترسن؛ هر وفت بیاد بیرون، فحش می‌ده، جیغ و ویغ می‌زنه. مثلا من اذیتش کردم، می‌آد سر فحش‌رو می‌کشه به تو. وقتی بچه‌ها بخوان لج‌شو در‌بیارن، می‌گن: «مرگ بر امریکا!» اونوقت اون هم حرصش می‌گیره، هزار تا فحش بی‌ناموسی و خوار و مادر می‌کشه به جون همه. ننه غلام دیونه‌س. بعضی وقتا هم با‌ آدم خوبه. یه روز من و زهرا رو گرفت به زور برد خونه‌ش، کله پاچه داد، گفت «بخورین!» ما هم خوردیم. ته کاسه یه لقمه موند که روش یه عالمه مو بود. گفت: «اگه نخورین با همین چاقو سرتونو می‌برم.» ما هم از ترس جونمون خوردیم. ننه غلام وقتی سر حاله، چیز می‌آره می‌ده آدم. مثلا یکی زخمه،‌ دوا می‌آره بهش می‌ده. مثلا کسی چیزی نداره، چیز می‌آره بهش می‌ده، وسط کوچه‌مون یه خونه‌س که دخترهاش خرابن، آقا. اونوقت شیره‌ای‌ها و چاقوکش‌ها می‌رن خونه‌شون، کار بد می‌کنن. بعضی وقتا هم دختر‌هاش لباس سرخ و زرد تن می‌کنن و کفش پاشنه بلند تق‌تقی می‌پوشن، می‌رن واسه بالاشهری‌ها قر می‌دن. یه دفعه هم داشتم می‌رفتم پیش بچه‌ها «لیس پس لیس» بازی کنم که دختر کوچیکه‌ش امیر ریزه رو صدا کرد و بهش گفت: «تو چقدر پاهات لاغره!» بعد امیر ریزه هم نامردی نکرد. گفت:«خودت چرا لمبه‌هات چاقه؟» بعد دوتایی کرکر خندیدن. خودم با همین دو تا چشمام دیدم، آقا! اونوقت ما هم که می‌بینیم محله‌مون پر از بی‌تربیتی‌یه، زدیم با هفت‌تا از بچه محلامون قهر کردیم. با اون هفت‌تا هم بمیرم آشتی نمی‌کنم، آقا. با یکی‌شون یه ساله قهریم، اسمش محمده. یه روز سر کوچه‌مون عروسی بود، ما هم داشتیم بازی می‌کردیم. من دراومدم به محمد گفتم: «محمد امشب چه خبره؟ آبجی‌ت می‌ره حجله؟» ناراحت شد، گفت: «باهات قهرم.» من هم گفتم: «چه بهتر! می‌رم درسامو می‌خونم.» به خدا ما چه می‌دونستیم، به خیالمون عروسی آبجیشه، آقا! فقط با دو نفر دوستیم: مهدی ملخ و حسن گامبو. مهدی از بس مردنیه، همه ملخ صداش می‌کنن. باباش قوری بست می‌زنه. وسط بازی یهو پیداش می‌شه، می‌آد می‌گه: «اگه منو بازی ندین، بازی‌تونو بهم می‌زنم.» اونوقت تا که دس بهش می‌خوره، جیغش می‌ره هوا، میگه: «گه خوردم، گه خوردم.» اونوقت می‌ره از حرصش با میخ یه شکل‌هایی می‌کشه روی دیوار، می‌گه: «این عکس کاظمه.» فسقلی فوتش کنی، قل می‌خوره، ها. آقا، ما دوچرخه خیلی دوست داریم، بعضی وقتا می‌ریم یه تومن می‌دیم چرخ کرایه می‌کنیم. حسن گامبو زورش می‌آد، با سنگ می‌زنه، می‌گه: «منو باید سوار کنی.» من هم می‌بینم داره دلش می‌شکنه، می‌گم: «بیا تو هم سوار شو!» داداش حسن گامبو پنج ماهه رفته لب مرز با خارجیا بجنگه. حسن می‌گه: «رفته امریکا رو نابود کنه، برگرده.» بابای حسن آهنکاره؛ یعنی قالب می‌سازه، پشقاب می‌سازه، همه‌چی می‌سازه. نه که حسن خیکیه، بچه‌ها صداش می‌کنن: «حسن گامبو، سرت تو شامپو!» می‌خواییم با این دو نفر هم قهر کنیم بره. هی می‌آن در خونمون داد می‌زنن: «کاظم، بیا بازی، بیا بازی!» بازی چیه، آقا؟ بده بچه بازی کنه. رفوزه بشیم چه کار؟ دلم می‌خواد دکتر، مهندس، بازنشست، نیرو هوایی، هرچی شد بشیم، بریم پی کارمون بره. ولی تو خونه ما نمی‌شه درس خوند. تا می‌آم بشینم، باید پاشم برم نون بخرم، جارو کنم، خشتک زهرامونو بشورم. پارسال که رفوزه شدم، همه‌ش نیم نمره می‌خواستم قبول بشم. مدرسه‌مونم خیلی هردمبیه، آقا! بچه‌هاش دزدن، می‌آن دفترامونو می‌دزدن. سر کلاس یکی گچ پرت می‌کنه، یکی رو نیمکت ضرب می‌گیره، یکی پا می‌شه می‌رقصه. ما هم که می‌بینیم خر تو خره، حوصله‌مون سر می‌ره، از مدرسه جیم می‌شیم، می‌ریم فروشگاه بزرگ. اونجا پله‌برقی داره. می‌ریم می‌ایستیم خودمونو می‌زینم به اون راه. الکی نگاه می‌کنیم به جنس منس‌ها؛ یعنی مثلا ما هم اومدیم چیز بخریم. بعد می‌ریم سوار پله‌برقی می‌شیم، می‌ریم سواری می‌خوریم، عشق می‌کنیم. آقا، اجازه؟ سه تا دایی هم دارم، آقا! یکی‌شون دایی ضامن، یکی‌شونم دایی مرتضی. اونی که وضعش خوبه اسمش دایی رضوانه. یه وانت داره با یه اتوشویی. تا پامونو می‌ذاریم در دکونش، نامرد یه لگد می‌زنه در اونجامون، می‌گه: «بزن به چاک! باز اومدی از دخل کف ببری» به خدا تهمت می‌زنه، آقا! آقا، به خدا هیچکی به اندازه ما از دزدی بدش نمی‌آد. آقا، دایی مرتضی‌مون اولها کارگر بلورسازی بود، ولی وقتی من هنوز تو دل مادرم بودم، افتاد زندان. یه شب هفت نفر ریختن سرش، اون هم چاقو کشید، زد یکی‌شونو کشت. بعد دادگاه هم اومد بیخودی تقصیر رو گذاشت گردن دایی ما. قبل انقلاب از زندان اومد بیرون، رفت معتاد شد. حالا هم همیشه با زنش دعوا مرافعه داره. گاهی می‌ذاره از خونه‌ش می‌ره، می‌ره می‌ره پیداش نمی‌شه. بعد که برمی‌گرده، الکی به زنش می‌گه، رفته بودم بیمارستان ترک کنم. دایی مرتضی یه بچه کوچولو داره، هروقت می‌آد خونمون، می‌خواد از پله‌هامون بره بالا، بیاد پایین. ما هم می‌ریم دنبالش که نیفته سرش بشکنه. می‌ریم بغلش می‌کنیم. اونوقت می‌ترسه، سفت آدمو می‌گیره. دایی ضامن‌مون توی دولت آباد نفتیه، بعضی روزها که می‌ره نفت پخش کنه منو هم با خودش می‌بره. اون تا می‌ره نفت بده به خونه‌ها، بچه‌ها می‌گیرن مسخره‌م می‌کنن، می‌گن: «ای عرب پا نفتی، کی اومدی، کی رفتی؟» سنگ می‌زنن تو کله‌ام. من هم که زورم نمی‌رسه، گریه‌م می‌گیره. یه روز رفتیم در یه خونه نفت بدیم، اونوقت یه پسره بود - لال بود - دنبالمون کرد تا سر کوچه‌شون. فحش مادر داد، گفت: «دیگه در خونه ما نیا!» لال بود، آقا! نمی‌دونیم چی می‌گفت... آقا، هر وقت از مادرمون حرف می‌زنیم، بغض می‌آد گلومونو می‌گیره، ول‌مون نمی‌کنه... مادرمون سر بچه مرد، آقا! شب درد بچه گرفتش. رفتیم نبات خانومو آوردیم. نبات خانوم مامای محله‌س، شله، یه چشمش هم چپه. صبح که بچه اومد دنیا، مادرمون گذاشت از دنیا رفت. بچه‌ هم پشت سرش مرد، آقا!... مادرمون اون وقت که زنده بود، توی کارخونهٔ استارلایت کار می‌کرد. جوراب شلواری می‌بافت. وقتی شکمش اومد بالا، از اونجا بیرونش کردن. مادرمون اینقده سختی کشیده که خدا بگه، بس! همیشه مریض بود، بعضی وقتا هم غش می‌کرد. پاهاش قد یه متکا باد کرده بود، آقا!... آقا، باور کن، آقا... وقتی مادرمون مرد ما صد برابر الان بغض کردیم. من و زهرا و مصطفی شب تا صبح خوابمون نبرد. بابام اون شب هزار تا سیگار کشید،‌ ولی صبحش مادرمون مرد. وقتی رفتیم خاکش کنیم، ننه غلام نمی‌خواست بذاره ما بریم تماشا، می‌گفت، ما بچه‌ایم، گناه داریم. ولی من دزدکی توی مرده‌شور خونه هم رفتم. بوی بدی می‌ده مرده‌شور خونه، بوی گربهٔ مرده. آدم می‌خواد دل و روده‌شو بالا بیاره. وقتی مادرمونو اوردن گذاشتن توی سالن مرده‌شور خونه، هفت تا مرده زودتر مرده بودن. مادرمون نفر هشتم بود. مرده‌ها منتظر بودن دوش خالی بشه، سر نوبت برن تو، غسل کنن. جنازه یه دختر مدرسه هم بود. نمی‌دونی فک و فامیل دختره چی‌کار می‌کردن؛ یکی سرشو می‌زد به دیوار، یکی کفش‌شو دراورده بود می‌زد تو سر خودش. مادرمونو که اوردن بذارن توی قبر، سروکله‌ٔ مصطفی هم پیداش شد. مادرمون با مصطفی خوب بود. خدا بیامرز که رفت توی قبر، نمی‌دونم از کجا یه مگس اومد نشست روی کفنش. تا مصطفی کیش‌اش کرد، مگسه گذاشت در رفت. بعد شروع کردن با بیل خاک ریختن روی سر مادرمون. رباب خانم با ناخن صورتشو می‌کند. بابام داشت توی دل خودش گریه می‌کرد. اگه مصطفی نمی‌زد زیر گریه و توی خاک و خل غلت نمی‌خورد، من هم گریه نمی‌کردم... مادرمونو که خاک کردیم، دم قبرستان حلوای نذری پخش می‌کردن. واسه اینکه بوی گربهٔ مرده از دماغم بره، یه قاشق حلوا گذاشتم دهنم. ولی صاحب عذا که روشو برگردوند، تفش کردم. آقا، هیچی نمی‌تونستیم بخوریم. آقا، ما دلمون خیلی تنگه، هیشکی نیست ما را زفت کنه. دل‌مون می‌خواد از این دنیا می‌رفتیم. آقا، باورتون نمی‌شه، توی محله ما ملت تند تند می‌میرن، آقا! زهرامون یه همبازی داره، همقد خودشه. اسمش الهامه، پنج سالشه. ده بیست روز پیش باباش از داربست افتاد زمین عکس برگردون شد، مرد. دیروز الهام اومده بود خونه‌مون، یه عکس از باباش هم اورده بود، می‌گفت، هر شب خواب باباشو می‌بینه که اون دنیا آتیش درست کرده، می‌خواد بیاد بگیره اونو کباب کنه بخوره. یه حرفهایی می‌زد که مو به تن آدم سیخ می‌شد. اونوقت شب که خوابم برد، خوابیدم، خواب دیدم عزرائیل و شمر با آتیش اومدن بالای سرم، هی می‌چرخن و چه‌چه می‌خندن. عزرائیل نصفه‌س، آقا! یعنی پا نداره. من هم اومدم از دست‌شون در برم که دیدم یه خرگوشه داره با مامانش قایم موشک بازی می‌کنه. رفتم بگم، من هم بازی که گذاشتن در رفتن. من هم دنبالشون کردم. خسته که شدم دیدم سوار یه قایقم، یه سگ هم داشتم. داشتم با سگ بازی می‌کردم که یهو امیر ریزه پشت پا انداخت، افتادم توی آب. من هم رفتم سوار دوچرخه شدم، زدم به چاک. سگ هم از توی قایق پرید، اومد دنبالم. بعدش دیدم یه هلی‌کوپتر بالای سرمه، می‌خواد باید بستنی لیوانی‌مو قاپ بزنه. من هم با سنگ زدم شیشه‌شو شکوندم. اون هم ترسید در رفت، توی کوچه دباغ‌ها غیب شد. بعدش دیدم عباس آقا گرگ شده، می‌خواد بیاد زهرامونو بگیره لقمه‌ٔ چپش کنه. از ترسم دویدم توی پارک و رفتم سوار تاب شدم. اینقده تاب بازی کردم تا حسابی سرم گیج رفت. اومدم از تاب بپرم پایین، دیدیم زیر پام یه چاهه، یه چاه به این گندگی. داشتم ول می‌شدم ته چاه که از خواب پریدم. نشستم گریه کردم. اونوقت بابام بیدار شد، پرسید: «باز چی شده؟ شاشیدی؟» گفتم: «می‌ترسم.» گفت: «بگیر بخواب بابا تو هم دلت خوشه!» من هم لحافو که کشیدم روی سرم، همه‌ش خدا خدا می‌کردم ایم دفعه که خوابم برد، شانسم بگه، بزنه خواب خوشبختی ببینم، دلم خوش بشه. ولی اگه ما شانس داشتیم، آقا، اسم‌مونو می‌ذاشتن شانسعلی.
diff --git a/utils/test-mixed.markup b/utils/test-mixed.markup
new file mode 100644
index 00000000..894a1b81
--- /dev/null
+++ b/utils/test-mixed.markup
@@ -0,0 +1,9 @@
+<span lang="zh-cn">你好,这是中文竖排测试。
+欢迎来到中国北京。
+白日依山尽,</span>
+<span lang="en">Roses are Red,
+Grass is Green. 2006</span>
+<span lang="fa">Arabic is گل‌ها قرمزند،‏
+چمن سبز. ۲۰۰۶</span>
+<span lang="zh-cn">ABC 白日依山尽, 2006</span>
+<span lang="ja">「ノートを買った。」</span>
diff --git a/utils/test-mixed.txt b/utils/test-mixed.txt
new file mode 100644
index 00000000..e23f0314
--- /dev/null
+++ b/utils/test-mixed.txt
@@ -0,0 +1,9 @@
+你好,这是中文竖排测试。
+欢迎来到中国北京。
+白日依山尽,
+Roses are Red,
+Grass is Green. 2006
+Arabic is گل‌ها قرمزند،‏
+چمن سبز. ۲۰۰۶
+ABC 白日依山尽, 2006
+「ノートを買った。」
diff --git a/utils/test-mongolian.txt b/utils/test-mongolian.txt
new file mode 100644
index 00000000..9117b7b1
--- /dev/null
+++ b/utils/test-mongolian.txt
@@ -0,0 +1,15 @@
+ᠡᠷᠲᠡ ᠤᠷᠢᠳᠠ ᠺᠠᠪᠠᠯᠢᠺ ᠪᠠᠯᠭᠠᠰᠤᠨ ᠳᠤᠷ ᠪᠢᠷᠠᠮᠠᠨ ᠤ ᠬᠠᠮᠤᠭ ᠤᠬᠠᠭᠠᠨ ᠤ ᠵᠦᠢᠯ ᠳᠦᠷ ᠮᠡᠷᠭᠡᠨ ᠪᠣᠯᠤᠭᠰᠠᠨ ᠰᠠᠢᠨ ᠲᠥᠷᠥᠯ ᠲᠦ ᠬᠡᠮᠡᠬᠦ
+ᠨᠢᠭᠡᠨ ᠪᠢᠷᠠᠮᠠᠨ ᠪᠦᠯᠦᠭᠡ᠃ ᠲᠡᠷᠡ ᠪᠢᠷᠠᠮᠠᠨ ᠳᠤᠷ ᠰᠡᠳᠬᠢᠯ ᠳᠤᠷ ᠲᠠᠭᠠᠯᠠᠬᠤ ᠨᠡᠷᠡᠲᠦ ᠨᠢᠭᠡᠨ ᠬᠠᠲᠤᠭᠲᠠᠢ ᠪᠦᠯᠦᠭᠡ᠃ ᠲᠡᠷᠡ ᠬᠣᠶᠠᠷ 
+ᠠᠴᠠ ᠨᠢᠭᠡᠨ ᠬᠥᠪᠡᠭᠦᠨ ᠲᠥᠷᠥᠵᠦᠬᠦᠢ᠃ ᠲᠡᠷᠡ ᠬᠥᠪᠡᠭᠦᠨ ᠢᠨᠤ ᠦᠴᠦᠭᠦᠬᠡᠨ ᠡᠴᠡ ᠭᠡᠭᠡᠨ ᠣᠶᠤᠲᠤ ᠶᠡᠬᠡᠰ ᠦᠨ ᠶᠠᠪᠤᠳᠠᠯ ᠢᠶᠠᠷ ᠶᠠᠪᠤᠭᠴᠢ
+ᠨᠢᠭᠡᠨ ᠪᠣᠯᠪᠠᠢ᠃ ᠲᠡᠨᠳᠡ ᠡᠬᠡ ᠢᠨᠤ ᠠᠶ᠎ᠠ ᠡᠨᠡ ᠬᠥᠪᠡᠭᠦᠨ ᠲᠥᠷᠥᠭᠡᠳ ᠰᠠᠴᠠ ᠶᠡᠬᠡᠰ ᠦᠨ ᠶᠠᠪᠤᠳᠠᠯ ᠢᠶᠠᠷ ᠶᠠᠪᠤᠭᠴᠢ ᠭᠡᠭᠡᠨ ᠣᠶᠤᠲᠤ
+ᠪᠦᠬᠦ ᠶᠢᠨ ᠲᠤᠯᠠ᠃ ᠮᠠᠭᠠᠳ ᠨᠢᠭᠡᠨ ᠵᠠᠶᠠᠭ᠋᠎ᠠ ᠲᠤ ᠪᠤᠢ ᠵ᠎ᠠ ᠬᠡᠮᠡᠵᠦ᠃ ᠭᠡᠢᠭᠰᠡᠨ ᠬᠡᠮᠡᠨ ᠨᠡᠷ᠎ᠡ ᠥᠭᠭᠦᠭᠡᠳ᠃ ᠠᠢ ᠡᠨᠡ ᠬᠥᠪᠡᠭᠦᠨ 
+ᠳ᠋ᠦᠷ ᠬᠠᠮᠤᠭ ᠤᠬᠠᠭᠠᠨ ᠤ ᠵᠦᠢᠯ ᠢ ᠰᠤᠷᠭᠠᠰᠤᠭᠠᠢ ᠬᠡᠮᠡᠵᠦ ᠪᠦᠷᠦᠨ᠃ ᠡᠴᠢᠭᠡ ᠡᠬᠡ ᠬᠣᠶᠠᠷ ᠪᠡᠷ ᠰᠤᠷᠭᠠᠭᠰᠠᠨ ᠳᠤᠷ᠃ ᠳᠥᠷᠪᠡᠯ
+ᠦᠭᠡᠢ ᠮᠡᠳᠡᠬᠦᠢ ᠨᠢᠭᠡᠨ ᠪᠣᠯᠪᠠᠢ᠃ ᠲᠡᠷᠡ ᠬᠥᠪᠡᠭᠦᠨ ᠢᠨᠤ ᠳᠣᠯᠣᠭᠠᠨ ᠵᠢᠯ ᠪᠣᠯᠤᠭᠰᠠᠨ ᠳᠤᠷ᠃ ᠪᠢᠷᠠᠮᠠᠨ ᠤ ᠤᠬᠠᠭᠠᠨ ᠤ ᠵᠦᠢᠯ ᠳᠦᠷ
+ᠮᠡᠷᠭᠡᠨ ᠪᠣᠯᠤᠭᠰᠠᠨ ᠡᠷᠳᠡᠮ ᠦᠳ ᠢᠨᠤ ᠬᠠᠮᠤᠭ ᠪᠦᠭᠦᠳᠡᠭᠡᠷ ᠲᠦᠷ ᠰᠣᠨᠣᠰᠲᠠᠭᠰᠠᠨ᠋᠎ᠠ᠃ ᠲᠡᠷᠡ ᠴᠠᠭ ᠲᠤᠷ ᠺᠠᠪᠠᠯᠢᠺ ᠪᠠᠯᠭᠠᠰᠤᠨ ᠳᠠᠬᠢᠨ ᠤᠬᠠᠭᠠᠨ ᠤ
+ᠵᠦᠢᠯ ᠳᠦᠷ ᠮᠡᠷᠭᠡᠨ ᠪᠣᠯᠤᠭᠰᠠᠨ ᠬᠠᠮᠤᠭ ᠪᠢᠷᠠᠮᠠᠨ ᠴᠢᠭᠤᠯᠵᠤ ᠤᠬᠠᠭᠠᠨ ᠤ ᠵᠦᠢᠯ ᠳᠦᠷ ᠲᠡᠮᠡᠴᠡᠭᠰᠡᠨ ᠳᠦᠷ᠃ ᠪᠢᠷᠠᠮᠠᠨ ᠤ ᠭᠡᠢᠭᠰᠡᠨ ᠬᠥᠪᠡᠭᠦᠨ
+ᠮᠠᠱᠢ ᠶᠡᠬᠡ ᠦᠯᠡᠮᠵᠢ ᠪᠣᠯᠤᠭᠰᠠᠨ ᠳᠤᠷ᠃ ᠲᠡᠳᠡᠭᠡᠷ ᠪᠢᠷᠠᠮᠠᠨ ᠥᠭᠦᠯᠡᠷ ᠦᠨ᠃ ᠠᠢ ᠬᠥᠪᠡᠭᠦᠨ ᠠ᠋ ᠨᠠᠰᠤᠨ ᠴᠢᠨᠤ ᠡᠢᠮᠦ ᠵᠠᠯᠠᠭᠤ
+ᠪᠥᠭᠡᠲᠡᠯ᠎ᠡ᠃ ᠤᠬᠠᠭᠠᠨ ᠤ ᠵᠦᠢᠯ ᠳᠦᠷ ᠡᠨᠡ ᠮᠡᠲᠦ ᠮᠡᠷᠭᠡᠨ ᠪᠣᠯᠤᠭᠰᠠᠨ ᠬᠡᠷ ᠪᠤᠶᠤ ᠬᠡᠮᠡᠨ ᠠᠰᠠᠭᠪᠠᠰᠤ᠃ ᠬᠥᠪᠡᠭᠦᠨ ᠥᠭᠦᠯᠡᠷ ᠦᠨ᠃
+ᠲᠣᠭᠣᠯᠤᠭᠰᠠᠨ ᠪᠤᠷᠬᠠᠨ ᠪᠡᠷ ᠪᠠᠭᠱᠢ ᠮᠢᠨᠤ ᠪᠤᠢ᠃ ᠪᠣᠭᠳᠠᠰ ᠤᠨ ᠨᠣᠮ ᠪᠡᠷ ᠢᠪᠡᠭᠡᠭᠴᠢ ᠮᠢᠨᠤ ᠪᠤᠢ᠃ ᠪᠤᠷᠰᠠᠩ ᠬᠤᠸᠠᠷᠠᠭ ᠤᠳ ᠪᠡᠷ
+ᠤᠳᠤᠷᠢᠳᠳᠤᠭᠴᠢ ᠮᠢᠨᠤ ᠪᠤᠢ᠃ ᠭᠤᠷᠪᠠᠨ ᠡᠷᠳᠡᠨᠢᠰ ᠦᠨ ᠠᠳᠢᠰᠲᠢᠳ ᠬᠢᠭᠰᠡᠨ ᠦ ᠬᠦᠴᠦ ᠪᠡᠷ ᠦᠢᠯᠡ ᠦᠷᠡᠰ ᠲᠦᠷ ᠦᠨᠡᠮᠱᠢᠭᠰᠡᠨ ᠦ ᠲᠤᠯᠠ᠃
+ᠰᠡᠱᠢᠭ ᠦᠭᠡᠢ ᠡᠷᠲᠡ ᠱᠢᠯᠲᠠᠭᠠᠨ ᠤ ᠬᠥᠷᠥᠩᠭᠡ ᠰᠠᠴᠤᠭᠰᠠᠨ ᠳᠤᠷ᠃ ᠦᠷ᠎ᠡ ᠦᠯᠦ ᠬᠣᠮᠰᠠᠳᠬᠤ ᠪᠤᠶᠠᠨ ᠢ ᠬᠢᠴᠢᠶᠡᠭᠰᠡᠨ ᠪᠤᠢ᠃ ᠨᠠᠰᠤᠨ
+ᠮᠢᠨᠤ ᠵᠠᠯᠠᠭᠤ ᠪᠣᠯᠪᠠᠰᠤ᠃ ᠤᠬᠠᠭᠠᠨ ᠤ ᠵᠦᠢᠯ ᠳᠦᠷ ᠮᠡᠷᠭᠡᠨ ᠪᠣᠯᠤᠭᠰᠠᠨ ᠤ ᠤᠴᠢᠷ ᠲᠡᠢᠮᠦ ᠪᠤᠢ ᠅ ᠅
diff --git a/utils/test-nko.txt b/utils/test-nko.txt
new file mode 100644
index 00000000..65a4347e
--- /dev/null
+++ b/utils/test-nko.txt
@@ -0,0 +1,15 @@
+ߝߘߊ߬ߝߌ߲߬ߠߊ߫ ߟߊ߬ߘߏ߲߬ߞߏ߬ߦߊ߬ߟߌ ߜߙߋ߬ߡߊ߬ߕߍ߰ߟߌ
+ߞߖ߭ߊߓߎߟߊ߲ߞߊ ߓߍ߲߬ߡߊ߬ߞߊ߲ ߠߎ߬ :
+
+ߝߘߊ߬ߝߌ߲߬ߠߊ߫ ߘߎ߰ߙߋ߲ ߠߎ߬ ߟߊ߫ ߢߐ߲߯ߓߍ߲ ߟߊ߬ߘߏ߲߬ߞߏ߬ߦߊ߬ߟߌ ߞߊߡߊ߬ ߞߖ߭ߊߓߎߟߊߟߊ߲ߞߊ߫߸ ( ߞߊ߬ߓߌߛߊ߬ ) ߣߌ߫ ( ߓߌ߬ߛ߭ߊ߯ߙߊ ) ߘߍ߬ߘߊ ߟߎ߬ ߟߊ߫ ߓߊ߯ߙߊ߫
+ߘߊߞߎ߲ ߡߌ߬ߘߊ߬ ߞߣߍ ߞߊ߲߬߸ (IDRC = ߞߣߊߘߊ߫ ߝߘߏ߬ߓߊ߬ ߡߣߊ߬ߙߏ߲߬ߠߌ߲ ߝߊ߲ߓߊ ߞ.ߝ.ߡ.ߝ.) ߞߊ߬ ߡߍ߲ ߖߐ߰ߣߍ ߓߐ߫ ߸ (MTDS =
+ߡߊ߬ߙߐߞߎ߫ ߖߊ߬ߥߏ ߟߊ߬ߥߙߎ߬ߞߌ߬ߟߌ ߗߋߢߊߟߌ ߡ.ߖ.ߥߗ.) ߝߊ߬ߙߊ ߘߐ߫ ߊ߬ ߞߊ߲߬ ( ߞߖ߭ߊߓߎߟߊ߲ߞߊ߫ ߕߎߞߑߣߏߔߊߞ ) ߘߍ߬ߞߙߎ ߝߊ߲ߓߊ ߝߍ߬.
+
+ߞߎ߲߬ߜߙߊ ߣߌ߲߬ ߞߊ߬ ߕߣߐ߬ߓߊ߫ ߛߐ߬ߘߐ߲߬ ߡߊߙߐߞߎ߬ ߖߡߊ߬ߣߊ ߡߊ߬ߞߐ߬ߙߐ߲߬ߞߐ߬ߙߐ߲߬ߠߌ ߞߏߕߌ߮ ߣߴߊ߬ ߣߐ߬ߝߍ߬ ߖߡߊ ߟߊ߫ ߸ ߏ߬ ߟߎ߬ ߡߍ߲ ߠߎ߫ ߣߊ߬ ߘߊ߫
+ߞߏߕߌ߯ ߟߎ߬ ߞߊ߲ߕߌ߮ ߞߐߦߌ߬ߘߊ߬، ߞߊ߬ ߕߐ߬ߣߐ߫ ߛߐ߬ߘߐ߲߬ ߞߣߊߘߊ߫ ߟߊ߬ߛߌ߰ߘߋ߲ ߝߣߊ߫ ߟߊ߫ ߣߊ߬ߟߌ ߣߌ߫ ߜߙߋ߫ ߜߘߍ ߘߎ߰ߙߋ߲߫ ߣߊ߬ߓߊ߮ ߟߎ߬ ߟߊ߫.
+
+ߕߋ߬ߟߋ߫ ߛߓߊ߬ ߖߊ߬ߣߍ߲߬ ߓߊ߯ߙߊ ߘߝߊߣߍ߲ ߞߐ߫ ߸ ߞߎ߲߬ߜߙߊ ߕߘߍ߬ߒ߬ߘߐ߬ߓߊ߰ߕߐ ߟߎ߫ ߓߍ߲߬ ߘߊ߫ ߞߊ߲߫ ߣߊ߬ߕߐ ߢߌ߲߬ ߠߎ߫ ߡߊ߬ :
+
+• ߕߟߋ߬ߓߊ߮ ߛߋߒߞߏߟߦߊ ߣߌ߫ ߟߐ߲ߕߊ ( ߕ.ߛ.ߟ. ICT ) ߘߊ߲߬ߠߊߕߍ߭ ߥߊߟߌ߫ ߞߊ߲߫ ߞߋߟߋ߲߫ ߡߊ߬ ߕߋ߲߬ ߸ ߏ߬ ߦߋ߫ ߡߌ߬ߙߌ߲߬ߘߌ ߟߎ߬ ߢߐ߲߯
+ߕߍ߫ ߞߣߍ ߝߏ߬ߣߊ߲ ߥߙߊ߬ ߟߊ߫ ߟߋ߬، ߞߊ߬ ߕߟߋ߬ߓߊ߮ ߟߐ߲ߠߌ߲ ߓߊߓߋ߭ ߥߎ߬ߛߎ ߖߊ߲߬ߧߊ߫ ߸ ߞߵߊ߬ ߞߊ߬ߙߊ߲ ߣߴߊ߬ ߘߊ ߜߟߍ߬ߦߊ߫، ߞߊ߬ߕߎ߯ ߸ ߊ߬
+ߘߌ߫ ߦߙߐ ߟߐ߲ߠߌ߲ߧߊ ߝߣߊ߫ ߖߐ߲ߝߐ߲߫ ߞߏߛߓߍ߫. \ No newline at end of file
diff --git a/utils/test-opentype-language.markup b/utils/test-opentype-language.markup
new file mode 100644
index 00000000..3d956aa4
--- /dev/null
+++ b/utils/test-opentype-language.markup
@@ -0,0 +1,6 @@
+Rendering U+0431 CYRILLIC SMALL LETTER BE
+using DejaVu Sans:
+<span font_desc="DejaVu Sans">
+Russian: <span lang="ru">б</span>
+Serbian: <span lang="sr">б</span>
+</span>
diff --git a/utils/test-syriac.txt b/utils/test-syriac.txt
new file mode 100644
index 00000000..05fedc7b
--- /dev/null
+++ b/utils/test-syriac.txt
@@ -0,0 +1,18 @@
+ܠܫܢܢ
+ܒܝܕܦܪܝܕܐ ܒܢܝܡܝܢ ܐܕܡ
+
+ܐܬܐܡܘܢ ܥܠܝܡܢ ܘܥܠܝܡܬܢ
+ܠܫܢܢ ܡܪܢܝܐ
+ܦܪܝܣܐ ܡܕܘܪܐ ܩܕܡܝܐ
+
+ܠܫܢܢ ܝܠܗ ܐܝܬܘܬܢ
+ܘܬܘܕܝܬܢ ܘܣܦܪܝܘܬܢ
+ܝܠܦܚܠܗ ܒܟܠ ܫܘܒ݂ܗܪܐ
+
+ܡܒܘܥܐ ܗܘܐ ܕܝܘܠܦܢܐ
+ܚܠܝܐ ܒܪܬܡܐ ܘܩܪܝܢܐ
+ܬܓ݂ܐ ܝܠܗ ܠܪܫܢ ܪܡܐ
+
+ܡܠܦܢܢ ܐܢܝ ܓܢܒܪܐ
+ܡܫܘܬܐܣܠܗܘܢ ܡܕܪܫܝܬܐ
+ܡܫܘܬܐܣܠܗܘܢ ܓܘ ܐܡܘܬܐ
diff --git a/utils/test-tamil.txt b/utils/test-tamil.txt
new file mode 100644
index 00000000..ff080a24
--- /dev/null
+++ b/utils/test-tamil.txt
@@ -0,0 +1,13 @@
+முருகன் அல்லது அழகு
+திரு வி.க.
+
+ குமரகுருபரர்
+
+உலகு குளிர எமது மதியில் ஒழுகு மமுத கிரணமே
+ உருகு மடிய ரிதய நெகிழ உணர்வி லெழுந லுதயமே
+கலையு நிறைவு மறிவு முதிர முதிரு மதுர நறவமே
+ கழுவு துகளர் முழுக நெடிய கருணை பெருகு சலதியே
+அலகில் புவன முடியும் வெளியில் அளியு மொளியி னிலயமே
+ அறிவு ளறிவை யறிவு மவரும் அறிய வரிய பிரமமே
+மலையின் மகள்கண் மணியை யனைய மதலை வருக வருகவே
+ வளமை தழுவு பரிதி புரியின் மருவு குமரன் வருகவே
diff --git a/utils/test-thai.txt b/utils/test-thai.txt
new file mode 100644
index 00000000..4bb8d2e0
--- /dev/null
+++ b/utils/test-thai.txt
@@ -0,0 +1,11 @@
+ลำดับนั้น เทวดากล่าวคาถากะพระมหาสัตว์ว่า:
+
+“การงานอันใด ยังไม่ถึงที่สุดด้วยความพยายาม การงานอันนั้นก็ไร้ผล มีความลำบากเกิดขึ้น การทำความพยายามในฐานะอันไม่สมควรใด จนความตายปรากฏขึ้น ความพยายามในฐานะอันไม่สมควรนั้น จะมีประโยชน์อะไร”
+
+เมื่อนางมณีเมขลากล่าวอย่างนี้แล้ว พระมหาสัตว์เมื่อจะทำนางมณีเมขลาให้จำนนต่อถ้อยคำ จึงได้ตรัสคาถาต่อไปว่า:
+
+“ดูก่อนเทวดา ผู้ใดรู้แจ้งการงานที่ทำจะไม่ลุล่วงไปได้จริง ๆ ชื่อว่าไม่รักษาชีวิตของตน ถ้าผู้นั้นละความเพียรในฐานะเช่นนั้นเสีย ก็จะพึงรู้ผลแห่งความเกียจคร้าน ดูก่อนเทวดา คนบางพวกในโลกนี้เห็นผลแห่งความประสงค์ของตน จึงประกอบการงานทั้งหลาย การงานเหล่านั้นจะสำเร็จหรือไม่ก็ตาม ดูก่อนเทวดา ท่านก็เห็นผลแห่งกรรมประจักษ์แก่ตนแล้วมิใช่หรือ คนอื่น ๆ จมในมหาสมุทรหมด เราคนเดียวยังว่ายข้ามอยู่ และได้เห็น ท่าน มาสถิตอยู่ใกล้ ๆ เรา เรานั้นจักพยายามตามสติกำลัง จักทำความเพียรที่บุรุษควรทำ ไปให้ถึงฝั่งแห่งมหาสมุทร”
+
+--
+พระมหาชนก
+พระราชนิพนธ์ พระบาทสมเด็จพระเจ้าอยู่หัว ภูมิพลอดุลยเดชฯ
diff --git a/utils/test-tibetan.txt b/utils/test-tibetan.txt
new file mode 100644
index 00000000..83d44aab
--- /dev/null
+++ b/utils/test-tibetan.txt
@@ -0,0 +1,12 @@
+Regular letters འབྲུག་རྒྱལ་ཁབ་འདི་དགའ་ཏོག་ཏོ་ཡོད།
+Reordering of Digits ༡༿༢༿༣༿ Illegal ཀ༿ སྐ༿༾
+Digits with other combining marks ༡༾༢༘༤༥༦ Illegal ཀ༘ཁ༙སྐ༘༙
+Tsa phru after base ཀ༹ Illegal ཀྱུ༹ ཀི༹
+Tsa phru after subjoined སྐ༹ རྒྱ༹
+A-Chung ཀཱ ཀྲཱ གླཱ གྱཱ༹ Illegal ཀཱུ ཀཱི
+Halanta ཀ྄ ཀ྄ཱ སྐ྄ སྐྱ྄
+Vowels ཀྱུ སྐྱིུ ཀྀ ཀེ ཀོ ལྐཻ ཀཽ
+Anusvara ཀུཾ ཀིཾ ཀཾི ཀིཾ ཀཾིཾ
+Visaraga ཀཿ Illegal ཀ༵ཿ
+Lower Stress Mark ཀ༷ ཀཱ༵
+Candrabindu ཀྃ ཀིྃ Illegal ཀིྃ
diff --git a/utils/viewer-cairo.c b/utils/viewer-cairo.c
new file mode 100644
index 00000000..ca98c1cd
--- /dev/null
+++ b/utils/viewer-cairo.c
@@ -0,0 +1,367 @@
+/* viewer-cairo.c: Common code for Cairo-based viewers
+ *
+ * Copyright (C) 1999,2004,2005 Red Hat, Inc.
+ * Copyright (C) 2001 Sun Microsystems
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include "config.h"
+
+#include "viewer-cairo.h"
+#include "viewer-render.h"
+
+#include <cairo.h>
+
+#include <string.h>
+
+
+
+#ifdef HAVE_CAIRO_XLIB
+#ifdef HAVE_XFT
+#include "viewer-x.h"
+#include <cairo-xlib.h>
+
+static cairo_surface_t *
+cairo_x_view_iface_create_surface (gpointer instance,
+ gpointer surface,
+ int width,
+ int height)
+{
+ XViewer *x = (XViewer *)instance;
+ Drawable drawable = (Drawable) surface;
+
+ return cairo_xlib_surface_create (x->display, drawable,
+ DefaultVisual (x->display, x->screen),
+ width, height);
+}
+
+static void
+cairo_x_view_iface_paint_background (gpointer instance G_GNUC_UNUSED,
+ cairo_t *cr)
+{
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ cairo_paint (cr);
+
+ if (opt_bg_set)
+ {
+ cairo_set_source_rgba (cr,
+ opt_bg_color.red / 65535.,
+ opt_bg_color.green / 65535.,
+ opt_bg_color.blue / 65535.,
+ opt_bg_alpha / 65535.);
+ cairo_paint (cr);
+ }
+}
+
+static CairoViewerIface cairo_x_viewer_iface = {
+ &x_viewer,
+ cairo_x_view_iface_create_surface,
+ cairo_x_view_iface_paint_background
+};
+#endif /* HAVE_XFT */
+#endif /* HAVE_CAIRO_XLIB */
+
+
+
+
+static cairo_surface_t *
+cairo_view_iface_create_surface (gpointer instance,
+ gpointer surface,
+ int width,
+ int height)
+{
+ return cairo_surface_reference (surface);
+}
+
+
+
+static gpointer
+cairo_image_view_create (const PangoViewer *klass G_GNUC_UNUSED)
+{
+ return NULL;
+}
+
+static void
+cairo_image_view_destroy (gpointer instance G_GNUC_UNUSED)
+{
+}
+
+static gpointer
+cairo_image_view_create_surface (gpointer instance,
+ int width,
+ int height)
+{
+ cairo_t *cr;
+ cairo_surface_t *surface;
+
+ /* TODO: Be smarter about format? */
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+
+ cr = cairo_create (surface);
+ cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+
+ return surface;
+}
+
+static void
+cairo_image_view_destroy_surface (gpointer instance,
+ gpointer surface)
+{
+ cairo_surface_destroy (surface);
+}
+
+const PangoViewer cairo_image_viewer = {
+ "CairoImage",
+ NULL,
+ NULL,
+ cairo_image_view_create,
+ cairo_image_view_destroy,
+ NULL,
+ cairo_image_view_create_surface,
+ cairo_image_view_destroy_surface,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static void
+cairo_image_view_iface_paint_background (gpointer instance G_GNUC_UNUSED,
+ cairo_t *cr)
+{
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ cairo_paint (cr);
+
+ if (opt_bg_set)
+ {
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source_rgba (cr,
+ opt_bg_color.red / 65535.,
+ opt_bg_color.green / 65535.,
+ opt_bg_color.blue / 65535.,
+ opt_bg_alpha / 65535.);
+ cairo_paint (cr);
+ }
+}
+
+static CairoViewerIface cairo_image_viewer_iface = {
+ &cairo_image_viewer,
+ cairo_view_iface_create_surface,
+ cairo_image_view_iface_paint_background
+};
+
+
+
+
+#ifdef CAIRO_HAS_SVG_SURFACE
+# include <cairo-svg.h>
+#endif
+#ifdef CAIRO_HAS_PDF_SURFACE
+# include <cairo-pdf.h>
+#endif
+#ifdef CAIRO_HAS_PS_SURFACE
+# include <cairo-ps.h>
+# if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
+# define HAS_EPS 1
+
+static cairo_surface_t *
+_cairo_eps_surface_create (const char *filename,
+ double width,
+ double height)
+{
+ cairo_surface_t *surface;
+
+ surface = cairo_ps_surface_create (filename, width, height);
+ cairo_ps_surface_set_eps (surface, TRUE);
+
+ return surface;
+}
+
+# else
+# undef HAS_EPS
+# endif
+#endif
+
+typedef cairo_surface_t *(*CairoVectorFileCreateFunc) (const char *filename,
+ double width,
+ double height);
+
+typedef struct
+{
+ const char *filename;
+ CairoVectorFileCreateFunc constructor;
+} CairoVectorViewer;
+
+static gpointer
+cairo_vector_view_create (const PangoViewer *klass G_GNUC_UNUSED)
+{
+ const char *extension = NULL;
+ CairoVectorFileCreateFunc constructor = NULL;
+
+ if (opt_output)
+ {
+ extension = strrchr (opt_output, '.');
+ if (extension)
+ extension++; /* skip the dot */
+ }
+
+ if (!extension)
+ return NULL;
+
+ if (0)
+ ;
+ #ifdef CAIRO_HAS_SVG_SURFACE
+ else if (0 == g_ascii_strcasecmp (extension, "svg"))
+ constructor = cairo_svg_surface_create;
+ #endif
+ #ifdef CAIRO_HAS_PDF_SURFACE
+ else if (0 == g_ascii_strcasecmp (extension, "pdf"))
+ constructor = cairo_pdf_surface_create;
+ #endif
+ #ifdef CAIRO_HAS_PS_SURFACE
+ else if (0 == g_ascii_strcasecmp (extension, "ps"))
+ constructor = cairo_ps_surface_create;
+ #ifdef HAS_EPS
+ else if (0 == g_ascii_strcasecmp (extension, "eps"))
+ constructor = _cairo_eps_surface_create;
+ #endif
+ #endif
+
+ if (constructor)
+ {
+ CairoVectorViewer *instance;
+
+ instance = g_slice_new (CairoVectorViewer);
+
+ /* save output filename and unset it such that the viewer layer
+ * doesn't try to save to file.
+ */
+ instance->filename = opt_output;
+ opt_output = NULL;
+
+ instance->constructor = constructor;
+
+ /* Fix dpi on 72. That's what cairo vector surfaces are. */
+ opt_dpi = 72;
+
+ return instance;
+ }
+
+ return NULL;
+}
+
+static void
+cairo_vector_view_destroy (gpointer instance G_GNUC_UNUSED)
+{
+ CairoVectorViewer *c = (CairoVectorViewer *) instance;
+
+ g_slice_free (CairoVectorViewer, c);
+}
+
+static gpointer
+cairo_vector_view_create_surface (gpointer instance,
+ int width,
+ int height)
+{
+ CairoVectorViewer *c = (CairoVectorViewer *) instance;
+ cairo_surface_t *surface;
+
+ surface = c->constructor (c->filename, width, height);
+
+ /*cairo_surface_set_fallback_resolution (surface, fallback_resolution_x, fallback_resolution_y);*/
+
+ return surface;
+}
+
+static void
+cairo_vector_view_destroy_surface (gpointer instance,
+ gpointer surface)
+{
+ /* TODO: check for errors */
+ cairo_surface_destroy (surface);
+}
+
+const PangoViewer cairo_vector_viewer = {
+ "CairoFile",
+ NULL,
+ NULL,
+ cairo_vector_view_create,
+ cairo_vector_view_destroy,
+ NULL,
+ cairo_vector_view_create_surface,
+ cairo_vector_view_destroy_surface,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static void
+cairo_vector_view_iface_paint_background (gpointer instance G_GNUC_UNUSED,
+ cairo_t *cr)
+{
+ if (opt_bg_set)
+ {
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source_rgba (cr,
+ opt_bg_color.red / 65535.,
+ opt_bg_color.green / 65535.,
+ opt_bg_color.blue / 65535.,
+ opt_bg_alpha / 65535.);
+ cairo_paint (cr);
+ }
+}
+
+static CairoViewerIface cairo_vector_viewer_iface = {
+ &cairo_vector_viewer,
+ cairo_view_iface_create_surface,
+ cairo_vector_view_iface_paint_background
+};
+
+
+
+gpointer
+cairo_viewer_iface_create (const CairoViewerIface **iface)
+{
+ gpointer ret;
+
+ *iface = &cairo_vector_viewer_iface;
+ ret = (*iface)->backend_class->create ((*iface)->backend_class);
+ if (ret)
+ return ret;
+
+#ifdef HAVE_CAIRO_XLIB
+#ifdef HAVE_XFT
+ if (opt_display)
+ {
+ *iface = &cairo_x_viewer_iface;
+ return (*iface)->backend_class->create ((*iface)->backend_class);
+ }
+#endif /* HAVE_XFT */
+#endif /* HAVE_CAIRO_XLIB */
+
+ *iface = &cairo_image_viewer_iface;
+ return (*iface)->backend_class->create ((*iface)->backend_class);
+}
+
+void
+cairo_viewer_add_options (GOptionGroup *group G_GNUC_UNUSED)
+{
+}
diff --git a/utils/viewer-cairo.h b/utils/viewer-cairo.h
new file mode 100644
index 00000000..e843664f
--- /dev/null
+++ b/utils/viewer-cairo.h
@@ -0,0 +1,47 @@
+/* viewer-cairo.h: Common headers for Cairo-based viewers
+ *
+ * Copyright (C) 1999,2004,2005 Red Hat, Inc.
+ * Copyright (C) 2001 Sun Microsystems
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef VIEWER_CAIRO_H
+#define VIEWER_CAIRO_H
+
+#include <cairo.h>
+
+#include "viewer.h"
+
+typedef struct _CairoViewerIface CairoViewerIface;
+
+struct _CairoViewerIface
+{
+ const PangoViewer *backend_class;
+
+ cairo_surface_t * (*create_surface) (gpointer instance,
+ gpointer surface,
+ int width,
+ int height);
+
+ void (*paint_background) (gpointer instance,
+ cairo_t *cr);
+};
+
+gpointer cairo_viewer_iface_create (const CairoViewerIface **iface_out);
+
+void cairo_viewer_add_options (GOptionGroup *group);
+
+#endif /* VIEWER_CAIRO_H */
diff --git a/utils/viewer-main.c b/utils/viewer-main.c
new file mode 100644
index 00000000..525599a7
--- /dev/null
+++ b/utils/viewer-main.c
@@ -0,0 +1,182 @@
+/* viewer-main.c: Main routine for viewers
+ *
+ * Copyright (C) 1999,2004,2005 Red Hat, Inc.
+ * Copyright (C) 2001 Sun Microsystems
+ * Copyright (C) 2006 Behdad Esfahbod
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include "config.h"
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <locale.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#ifdef G_OS_UNIX
+#include <sys/wait.h>
+#endif
+
+#include "viewer.h"
+#include "viewer-render.h"
+
+int
+main (int argc,
+ char **argv)
+{
+ const PangoViewer *view;
+ gpointer instance;
+ PangoContext *context;
+ int run;
+ int width, height;
+ gpointer surface;
+
+#if !GLIB_CHECK_VERSION (2, 35, 3)
+ g_type_init();
+#endif
+ g_set_prgname ("pango-view");
+ setlocale (LC_ALL, "");
+ parse_options (argc, argv);
+
+ view = opt_viewer;
+
+ g_assert (view->id);
+
+ instance = view->create (view);
+ context = view->get_context (instance);
+ width = height = 1;
+ surface = view->create_surface (instance, width, height);
+ view->render (instance, surface, context, &width, &height, NULL);
+ view->destroy_surface (instance, surface);
+ surface = view->create_surface (instance, width, height);
+ for (run = 0; run < MAX(1,opt_runs); run++)
+ view->render (instance, surface, context, &width, &height, NULL);
+
+ if (opt_output)
+ {
+ if (!view->write)
+ fail ("%s viewer backend does not support writing", view->name);
+ else
+ {
+ FILE *stream;
+ GPid pid = 0;
+
+ if (view->write_suffix && g_str_has_suffix (opt_output, view->write_suffix))
+ {
+ stream = g_fopen (opt_output, "wb");
+ if (!stream)
+ fail ("Cannot open output file %s: %s\n",
+ opt_output, g_strerror (errno));
+ }
+ else
+ {
+ int fd;
+ const gchar *convert_argv[4] = {"convert", "-", "%s"};
+ GError *error;
+
+ convert_argv[2] = opt_output;
+
+ if (!g_spawn_async_with_pipes (NULL, (gchar **)(void*)convert_argv, NULL,
+ G_SPAWN_DO_NOT_REAP_CHILD |
+ G_SPAWN_SEARCH_PATH |
+ G_SPAWN_STDOUT_TO_DEV_NULL |
+ G_SPAWN_STDERR_TO_DEV_NULL,
+ NULL, NULL, &pid, &fd, NULL, NULL, &error))
+ fail ("When running ImageMagick 'convert' command: %s\n", error->message);
+ stream = fdopen (fd, "wb");
+ }
+ view->write (instance, surface, stream, width, height);
+ fclose (stream);
+#ifdef G_OS_UNIX
+ if (pid)
+ waitpid (pid, NULL, 0);
+#endif
+ }
+ }
+
+ if (opt_display)
+ {
+ char *title;
+ title = get_options_string ();
+
+ if (view->display)
+ {
+ gpointer window = NULL;
+ gpointer state = NULL;
+
+ if (view->create_window)
+ {
+ window = view->create_window (instance, title, width, height);
+ if (!window)
+ goto no_display;
+ }
+
+ opt_display = FALSE;
+ while (1)
+ {
+ state = view->display (instance, surface, window, width, height, state);
+ if (state == GINT_TO_POINTER (-1))
+ break;
+
+ view->render (instance, surface, context, &width, &height, state);
+ }
+
+ if (view->destroy_window)
+ view->destroy_window (instance, window);
+ }
+no_display:
+
+ /* If failed to display natively, call ImageMagick */
+ if (opt_display)
+ {
+ int fd;
+ FILE *stream;
+ const gchar *display_argv[5] = {"display", "-title", "%s", "-"};
+ GError *error = NULL;
+ GPid pid;
+
+ if (!view->write)
+ fail ("%s viewer backend does not support displaying or writing", view->name);
+ display_argv[2] = title;
+
+ if (!g_spawn_async_with_pipes (NULL, (gchar **)(void*)display_argv, NULL,
+ G_SPAWN_DO_NOT_REAP_CHILD |
+ G_SPAWN_SEARCH_PATH |
+ G_SPAWN_STDOUT_TO_DEV_NULL |
+ G_SPAWN_STDERR_TO_DEV_NULL,
+ NULL, NULL, &pid, &fd, NULL, NULL, &error))
+ fail ("When running ImageMagick 'display' command: %s\n", error->message);
+ stream = fdopen (fd, "wb");
+ view->write (instance, surface, stream, width, height);
+ fclose (stream);
+#ifdef G_OS_UNIX
+ waitpid (pid, NULL, 0);
+#endif
+ g_spawn_close_pid (pid);
+ }
+
+ g_free (title);
+ }
+
+ view->destroy_surface (instance, surface);
+ g_object_unref (context);
+ view->destroy (instance);
+ finalize ();
+ return 0;
+}
diff --git a/utils/viewer-pangocairo.c b/utils/viewer-pangocairo.c
new file mode 100644
index 00000000..3e893207
--- /dev/null
+++ b/utils/viewer-pangocairo.c
@@ -0,0 +1,462 @@
+/* viewer-pangocairo.c: PangoCairo viewer backend.
+ *
+ * Copyright (C) 1999,2004,2005 Red Hat, Inc.
+ * Copyright (C) 2001 Sun Microsystems
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "viewer-render.h"
+#include "viewer-cairo.h"
+
+#include <pango/pangocairo.h>
+
+static int opt_annotate = 0;
+
+typedef struct
+{
+ const CairoViewerIface *iface;
+
+ gpointer backend;
+
+ PangoFontMap *fontmap;
+ cairo_font_options_t *font_options;
+} CairoViewer;
+
+static gpointer
+pangocairo_view_create (const PangoViewer *klass G_GNUC_UNUSED)
+{
+ CairoViewer *instance;
+
+ instance = g_slice_new (CairoViewer);
+
+ instance->backend = cairo_viewer_iface_create (&instance->iface);
+
+ instance->fontmap = pango_cairo_font_map_new ();
+ pango_cairo_font_map_set_resolution (PANGO_CAIRO_FONT_MAP (instance->fontmap), opt_dpi);
+
+ instance->font_options = cairo_font_options_create ();
+ if (opt_hinting != HINT_DEFAULT)
+ {
+ if (opt_hinting == HINT_NONE)
+ cairo_font_options_set_hint_style (instance->font_options, CAIRO_HINT_STYLE_NONE);
+ else if (opt_hinting == HINT_FULL)
+ cairo_font_options_set_hint_style (instance->font_options, CAIRO_HINT_STYLE_FULL);
+ }
+
+ return instance;
+}
+
+static void
+pangocairo_view_destroy (gpointer instance)
+{
+ CairoViewer *c = (CairoViewer *) instance;
+
+ cairo_font_options_destroy (c->font_options);
+
+ g_object_unref (c->fontmap);
+
+ c->iface->backend_class->destroy (c->backend);
+
+ cairo_debug_reset_static_data ();
+
+ g_slice_free (CairoViewer, c);
+}
+
+static PangoContext *
+pangocairo_view_get_context (gpointer instance)
+{
+ CairoViewer *c = (CairoViewer *) instance;
+ PangoContext *context;
+
+ context = pango_font_map_create_context (c->fontmap);
+ pango_cairo_context_set_font_options (context, c->font_options);
+
+ return context;
+}
+
+typedef struct
+{
+ gpointer backend;
+
+ cairo_surface_t *cairo;
+} CairoSurface;
+
+static gpointer
+pangocairo_view_create_surface (gpointer instance,
+ int width,
+ int height)
+{
+ CairoViewer *c = (CairoViewer *) instance;
+ CairoSurface *surface;
+
+ surface = g_slice_new (CairoSurface);
+
+ surface->backend = c->iface->backend_class->create_surface (c->backend,
+ width, height);
+
+ surface->cairo = c->iface->create_surface (c->backend,
+ surface->backend,
+ width, height);
+
+ return surface;
+}
+
+static void
+pangocairo_view_destroy_surface (gpointer instance,
+ gpointer surface)
+{
+ CairoViewer *c = (CairoViewer *) instance;
+ CairoSurface *c_surface = (CairoSurface *) surface;
+
+ c->iface->backend_class->destroy_surface (c->backend, c_surface->backend);
+ cairo_surface_destroy (c_surface->cairo);
+
+ g_slice_free (CairoSurface, surface);
+}
+
+static void
+render_callback (PangoLayout *layout,
+ int x,
+ int y,
+ gpointer context,
+ gpointer state)
+{
+ cairo_t *cr = (cairo_t *) context;
+ int annotate = (GPOINTER_TO_INT (state) + opt_annotate) % 3;
+
+ cairo_save (cr);
+ cairo_translate (cr, x, y);
+
+ if (annotate)
+ {
+ cairo_pattern_t *pattern;
+ PangoRectangle ink, logical;
+ double lw = cairo_get_line_width (cr);
+ PangoLayoutIter* iter;
+
+ pango_layout_get_extents (layout, &ink, &logical);
+
+ if (annotate >= 2)
+ {
+ /* draw resolved gravity "roof" in blue */
+ cairo_save (cr);
+ cairo_translate (cr,
+ (double)logical.x / PANGO_SCALE,
+ (double)logical.y / PANGO_SCALE);
+ cairo_scale (cr,
+ (double)logical.width / PANGO_SCALE * 0.5,
+ (double)logical.height / PANGO_SCALE * 0.5);
+ cairo_translate (cr, 1.0, 1.0);
+ cairo_rotate (cr,
+ pango_gravity_to_rotation (
+ pango_context_get_gravity (
+ pango_layout_get_context (layout))));
+ cairo_move_to (cr, -1.0, -1.0);
+ cairo_rel_line_to (cr, +1.0, -0.2); /* / */
+ cairo_rel_line_to (cr, +1.0, +0.2); /* \ */
+ cairo_close_path (cr); /* - */
+ pattern = cairo_pattern_create_linear (0, -1.0, 0, -1.2);
+ cairo_pattern_add_color_stop_rgba (pattern, 0.0, 0.0, 0.0, 1.0, 0.0);
+ cairo_pattern_add_color_stop_rgba (pattern, 1.0, 0.0, 0.0, 1.0, 0.15);
+ cairo_set_source (cr, pattern);
+ cairo_fill (cr);
+ /* once more, without close_path this time */
+ cairo_move_to (cr, -1.0, -1.0);
+ cairo_rel_line_to (cr, +1.0, -0.2); /* / */
+ cairo_rel_line_to (cr, +1.0, +0.2); /* \ */
+ /* silly line_width is not locked :(. get rid of scale. */
+ cairo_restore (cr);
+ cairo_save (cr);
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.7, 0.2);
+ cairo_stroke (cr);
+ cairo_restore (cr);
+
+
+ /* draw block progression arrow in green */
+ cairo_save (cr);
+ cairo_translate (cr,
+ (double)logical.x / PANGO_SCALE,
+ (double)logical.y / PANGO_SCALE);
+ cairo_scale (cr,
+ (double)logical.width / PANGO_SCALE * 0.5,
+ (double)logical.height / PANGO_SCALE * 0.5);
+ cairo_translate (cr, 1.0, 1.0);
+ cairo_move_to (cr, -0.4, -0.7);
+ cairo_rel_line_to (cr, +0.8, 0.0); /* -- */
+ cairo_rel_line_to (cr, 0.0, +0.9); /* | */
+ cairo_rel_line_to (cr, +0.4, 0.0); /* - */
+ cairo_rel_line_to (cr, -0.8, +0.5); /* / */
+ cairo_rel_line_to (cr, -0.8, -0.5); /* \ */
+ cairo_rel_line_to (cr, +0.4, 0.0); /* - */
+ cairo_close_path (cr); /* | */
+ pattern = cairo_pattern_create_linear (0, -0.7, 0, 0.7);
+ cairo_pattern_add_color_stop_rgba (pattern, 0.0, 0.0, 1.0, 0.0, 0.0);
+ cairo_pattern_add_color_stop_rgba (pattern, 1.0, 0.0, 1.0, 0.0, 0.15);
+ cairo_set_source (cr, pattern);
+ cairo_fill_preserve (cr);
+ /* silly line_width is not locked :(. get rid of scale. */
+ cairo_restore (cr);
+ cairo_save (cr);
+ cairo_set_source_rgba (cr, 0.0, 0.7, 0.0, 0.2);
+ cairo_stroke (cr);
+ cairo_restore (cr);
+ }
+
+ /* draw baselines with line direction arrow in orange */
+ cairo_save (cr);
+ cairo_set_source_rgba (cr, 1.0, 0.5, 0.0, 0.5);
+ iter = pango_layout_get_iter (layout);
+ do
+ {
+ PangoLayoutLine *line = pango_layout_iter_get_line (iter);
+ double width = (double)logical.width / PANGO_SCALE;
+
+ y = pango_layout_iter_get_baseline (iter);
+ cairo_save (cr);
+ cairo_translate (cr,
+ (double)logical.x / PANGO_SCALE + width * 0.5,
+ (double)y / PANGO_SCALE);
+ if (line->resolved_dir)
+ cairo_scale (cr, -1, 1);
+ cairo_move_to (cr, -width * .5, -lw*0.2);
+ cairo_rel_line_to (cr, +width * .9, -lw*0.3);
+ cairo_rel_line_to (cr, 0, -lw);
+ cairo_rel_line_to (cr, +width * .1, +lw*1.5);
+ cairo_rel_line_to (cr, -width * .1, +lw*1.5);
+ cairo_rel_line_to (cr, 0, -lw);
+ cairo_rel_line_to (cr, -width * .9, -lw*0.3);
+ cairo_close_path (cr);
+ cairo_fill (cr);
+ cairo_restore (cr);
+ }
+ while (pango_layout_iter_next_line (iter));
+ pango_layout_iter_free (iter);
+ cairo_restore (cr);
+
+ /* draw the logical rect in red */
+ cairo_save (cr);
+ cairo_set_source_rgba (cr, 1.0, 0.0, 0.0, 0.5);
+ cairo_rectangle (cr,
+ (double)logical.x / PANGO_SCALE - lw / 2,
+ (double)logical.y / PANGO_SCALE - lw / 2,
+ (double)logical.width / PANGO_SCALE + lw,
+ (double)logical.height / PANGO_SCALE + lw);
+ cairo_stroke (cr);
+ cairo_restore (cr);
+
+ /* draw the ink rect in green */
+ cairo_save (cr);
+ cairo_set_source_rgba (cr, 0.0, 1.0, 0.0, 0.5);
+ cairo_rectangle (cr,
+ (double)ink.x / PANGO_SCALE - lw / 2,
+ (double)ink.y / PANGO_SCALE - lw / 2,
+ (double)ink.width / PANGO_SCALE + lw,
+ (double)ink.height / PANGO_SCALE + lw);
+ cairo_stroke (cr);
+ cairo_restore (cr);
+ }
+
+ cairo_move_to (cr, 0, 0);
+ pango_cairo_show_layout (cr, layout);
+
+ cairo_restore (cr);
+
+ cairo_surface_flush (cairo_get_target (cr));
+}
+
+static void
+transform_callback (PangoContext *context,
+ PangoMatrix *matrix,
+ gpointer cr_context,
+ gpointer state G_GNUC_UNUSED)
+{
+ cairo_t *cr = (cairo_t *)cr_context;
+ cairo_matrix_t cairo_matrix;
+
+ if (matrix)
+ {
+ cairo_matrix.xx = matrix->xx;
+ cairo_matrix.yx = matrix->yx;
+ cairo_matrix.xy = matrix->xy;
+ cairo_matrix.yy = matrix->yy;
+ cairo_matrix.x0 = matrix->x0;
+ cairo_matrix.y0 = matrix->y0;
+ }
+ else
+ {
+ cairo_matrix_init_identity (&cairo_matrix);
+ }
+
+ cairo_set_matrix (cr, &cairo_matrix);
+
+ pango_cairo_update_context (cr, context);
+}
+
+static void
+pangocairo_view_render (gpointer instance,
+ gpointer surface,
+ PangoContext *context,
+ int *width,
+ int *height,
+ gpointer state)
+{
+ CairoViewer *c = (CairoViewer *) instance;
+ cairo_t *cr;
+ CairoSurface *c_surface = (CairoSurface *) surface;
+
+ g_assert (surface);
+
+ cr = cairo_create (c_surface->cairo);
+
+ transform_callback (context, NULL, cr, state);
+
+ c->iface->paint_background (instance, cr);
+
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ cairo_set_source_rgba (cr,
+ opt_fg_color.red / 65535.,
+ opt_fg_color.green / 65535.,
+ opt_fg_color.blue / 65535.,
+ opt_fg_alpha / 65535.);
+
+ do_output (context, render_callback, transform_callback, cr, state, width, height);
+
+ cairo_destroy (cr);
+}
+
+#ifdef HAVE_CAIRO_PNG
+static cairo_status_t
+write_func (void *closure,
+ const unsigned char *data,
+ unsigned int length)
+{
+ FILE *stream = (FILE *) closure;
+ unsigned int l;
+
+ l = fwrite (data, 1, length, stream);
+
+ return l == length ? CAIRO_STATUS_SUCCESS : CAIRO_STATUS_WRITE_ERROR;
+}
+
+static void
+pangocairo_view_write (gpointer instance G_GNUC_UNUSED,
+ gpointer surface,
+ FILE *stream,
+ int width G_GNUC_UNUSED,
+ int height G_GNUC_UNUSED)
+{
+ CairoSurface *c_surface = (CairoSurface *) surface;
+
+ cairo_surface_write_to_png_stream (c_surface->cairo, write_func, stream);
+}
+#endif
+
+static gpointer
+pangocairo_view_create_window (gpointer instance,
+ const char *title,
+ int width,
+ int height)
+{
+ CairoViewer *c = (CairoViewer *) instance;
+
+ if (!c->iface->backend_class->create_window)
+ return NULL;
+
+ return c->iface->backend_class->create_window (c->backend,
+ title,
+ width, height);
+}
+
+static void
+pangocairo_view_destroy_window (gpointer instance,
+ gpointer window)
+{
+ CairoViewer *c = (CairoViewer *) instance;
+
+ c->iface->backend_class->destroy_window (c->backend,
+ window);
+}
+
+static gpointer
+pangocairo_view_display (gpointer instance,
+ gpointer surface,
+ gpointer window,
+ int width, int height,
+ gpointer state)
+{
+ CairoViewer *c = (CairoViewer *) instance;
+ CairoSurface *c_surface = (CairoSurface *) surface;
+
+ return c->iface->backend_class->display (c->backend,
+ c_surface->backend,
+ window,
+ width, height,
+ state);
+}
+
+static GOptionGroup *
+pangocairo_view_get_option_group (const PangoViewer *klass G_GNUC_UNUSED)
+{
+ GOptionEntry entries[] =
+ {
+ {"annotate", 0, 0, G_OPTION_ARG_INT, &opt_annotate,
+ "Annotate the output", "1 or 2"},
+ {NULL}
+ };
+ GOptionGroup *group;
+
+ group = g_option_group_new ("cairo",
+ "Cairo backend options:",
+ "Options understood by the cairo backend",
+ NULL,
+ NULL);
+
+ g_option_group_add_entries (group, entries);
+
+ cairo_viewer_add_options (group);
+
+ return group;
+}
+
+const PangoViewer pangocairo_viewer = {
+ "PangoCairo",
+ "cairo",
+#ifdef HAVE_CAIRO_PNG
+ "png",
+#else
+ NULL,
+#endif
+ pangocairo_view_create,
+ pangocairo_view_destroy,
+ pangocairo_view_get_context,
+ pangocairo_view_create_surface,
+ pangocairo_view_destroy_surface,
+ pangocairo_view_render,
+#ifdef HAVE_CAIRO_PNG
+ pangocairo_view_write,
+#else
+ NULL,
+#endif
+ pangocairo_view_create_window,
+ pangocairo_view_destroy_window,
+ pangocairo_view_display,
+ NULL,
+ NULL,
+ pangocairo_view_get_option_group
+};
diff --git a/utils/viewer-pangoft2.c b/utils/viewer-pangoft2.c
new file mode 100644
index 00000000..87973c80
--- /dev/null
+++ b/utils/viewer-pangoft2.c
@@ -0,0 +1,162 @@
+/* viewer-pangoft2.c: PangoFT2 viewer backend.
+ *
+ * Copyright (C) 1999,2004,2005 Red Hat, Inc.
+ * Copyright (C) 2001 Sun Microsystems
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "viewer-render.h"
+#include "viewer.h"
+
+#include <pango/pangoft2.h>
+
+static void
+substitute_func (FcPattern *pattern,
+ gpointer data G_GNUC_UNUSED)
+{
+ if (opt_hinting != HINT_DEFAULT)
+ {
+ FcPatternDel (pattern, FC_HINTING);
+ FcPatternAddBool (pattern, FC_HINTING, opt_hinting != HINT_NONE);
+
+ FcPatternDel (pattern, FC_AUTOHINT);
+ FcPatternAddBool (pattern, FC_AUTOHINT, opt_hinting == HINT_AUTO);
+ }
+}
+
+static gpointer
+pangoft2_view_create (const PangoViewer *klass G_GNUC_UNUSED)
+{
+ PangoFontMap *fontmap;
+ fontmap = pango_ft2_font_map_new ();
+
+ pango_ft2_font_map_set_resolution (PANGO_FT2_FONT_MAP (fontmap), opt_dpi, opt_dpi);
+ pango_ft2_font_map_set_default_substitute (PANGO_FT2_FONT_MAP (fontmap), substitute_func, NULL, NULL);
+
+ return fontmap;
+}
+
+static void
+pangoft2_view_destroy (gpointer instance)
+{
+ g_object_unref (instance);
+}
+
+static PangoContext *
+pangoft2_view_get_context (gpointer instance)
+{
+ return pango_font_map_create_context (PANGO_FONT_MAP (instance));
+}
+
+static gpointer
+pangoft2_view_create_surface (gpointer instance G_GNUC_UNUSED,
+ int width,
+ int height)
+{
+ FT_Bitmap *bitmap;
+
+ bitmap = g_slice_new (FT_Bitmap);
+ bitmap->width = width;
+ bitmap->pitch = (bitmap->width + 3) & ~3;
+ bitmap->rows = height;
+ bitmap->buffer = g_malloc (bitmap->pitch * bitmap->rows);
+ bitmap->num_grays = 256;
+ bitmap->pixel_mode = ft_pixel_mode_grays;
+ memset (bitmap->buffer, 0x00, bitmap->pitch * bitmap->rows);
+
+ return bitmap;
+}
+
+static void
+pangoft2_view_destroy_surface (gpointer instance G_GNUC_UNUSED,
+ gpointer surface)
+{
+ FT_Bitmap *bitmap = (FT_Bitmap *) surface;
+
+ g_free (bitmap->buffer);
+ g_slice_free (FT_Bitmap, bitmap);
+}
+
+static void
+render_callback (PangoLayout *layout,
+ int x,
+ int y,
+ gpointer context,
+ gpointer state G_GNUC_UNUSED)
+{
+ pango_ft2_render_layout ((FT_Bitmap *)context,
+ layout,
+ x, y);
+}
+
+static void
+pangoft2_view_render (gpointer instance G_GNUC_UNUSED,
+ gpointer surface,
+ PangoContext *context,
+ int *width,
+ int *height,
+ gpointer state)
+{
+ int pix_idx;
+ FT_Bitmap *bitmap = (FT_Bitmap *) surface;
+
+ do_output (context, render_callback, NULL, surface, state, width, height);
+
+ for (pix_idx=0; pix_idx<bitmap->pitch * bitmap->rows; pix_idx++)
+ bitmap->buffer[pix_idx] = 255 - bitmap->buffer[pix_idx];
+}
+
+static void
+pangoft2_view_write (gpointer instance G_GNUC_UNUSED,
+ gpointer surface,
+ FILE *stream,
+ int width,
+ int height)
+{
+ int row;
+ FT_Bitmap *bitmap = (FT_Bitmap *) surface;
+
+ /* Write it as pgm to output */
+ fprintf(stream,
+ "P5\n"
+ "%d %d\n"
+ "255\n", width, height);
+ for (row = 0; row < height; row++)
+ fwrite (bitmap->buffer + row * bitmap->pitch, 1, width, stream);
+}
+
+const PangoViewer pangoft2_viewer = {
+ "PangoFT2",
+ "ft2",
+ ".pgm",
+ pangoft2_view_create,
+ pangoft2_view_destroy,
+ pangoft2_view_get_context,
+ pangoft2_view_create_surface,
+ pangoft2_view_destroy_surface,
+ pangoft2_view_render,
+ pangoft2_view_write,
+ NULL,
+ NULL,
+ NULL
+};
diff --git a/utils/viewer-pangoxft.c b/utils/viewer-pangoxft.c
new file mode 100644
index 00000000..948cd90e
--- /dev/null
+++ b/utils/viewer-pangoxft.c
@@ -0,0 +1,154 @@
+/* viewer-pangoxft.c: PangoXft viewer backend.
+ *
+ * Copyright (C) 1999,2004,2005 Red Hat, Inc.
+ * Copyright (C) 2001 Sun Microsystems
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include "config.h"
+
+#include "viewer-render.h"
+#include "viewer-x.h"
+
+#include <pango/pangoxft.h>
+
+static void
+default_substitute (FcPattern *pattern,
+ gpointer data G_GNUC_UNUSED)
+{
+ FcPatternDel (pattern, FC_DPI);
+ FcPatternAddInteger (pattern, FC_DPI, opt_dpi);
+
+ if (opt_hinting != HINT_DEFAULT)
+ {
+ FcPatternDel (pattern, FC_HINTING);
+ FcPatternAddBool (pattern, FC_HINTING, opt_hinting != HINT_NONE);
+
+ FcPatternDel (pattern, FC_AUTOHINT);
+ FcPatternAddBool (pattern, FC_AUTOHINT, opt_hinting == HINT_AUTO);
+ }
+}
+
+static gpointer
+pangoxft_view_create (const PangoViewer *klass)
+{
+ XViewer *instance;
+
+ instance = x_view_create (klass);
+
+ XftInit (NULL);
+
+ pango_xft_set_default_substitute (instance->display, instance->screen,
+ default_substitute, NULL, NULL);
+
+ return instance;
+}
+
+static void
+pangoxft_view_destroy (gpointer instance)
+{
+ XViewer *x = (XViewer *)instance;
+
+ pango_xft_shutdown_display (x->display, x->screen);
+
+ x_view_destroy (instance);
+}
+
+static PangoContext *
+pangoxft_view_get_context (gpointer instance)
+{
+ XViewer *x = (XViewer *) instance;
+
+ return pango_font_map_create_context (pango_xft_get_font_map (x->display, x->screen));
+}
+
+typedef struct
+{
+ XftDraw *draw;
+ XftColor color;
+} MyXftContext;
+
+static void
+render_callback (PangoLayout *layout,
+ int x,
+ int y,
+ gpointer context,
+ gpointer state G_GNUC_UNUSED)
+{
+ MyXftContext *xft_context = (MyXftContext *) context;
+
+ pango_xft_render_layout (xft_context->draw,
+ &xft_context->color,
+ layout,
+ x * PANGO_SCALE, y * PANGO_SCALE);
+}
+
+static void
+pangoxft_view_render (gpointer instance,
+ gpointer surface,
+ PangoContext *context,
+ int *width,
+ int *height,
+ gpointer state)
+{
+ XViewer *x = (XViewer *) instance;
+ Pixmap pixmap = (Pixmap) surface;
+ MyXftContext xft_context;
+ XftDraw *draw;
+ XftColor color;
+
+ draw = XftDrawCreate (x->display, pixmap,
+ DefaultVisual (x->display, x->screen),
+ DefaultColormap (x->display, x->screen));
+
+ /* XftDrawRect only fills solid.
+ * Flatten with white.
+ */
+ color.color.red = ((opt_bg_color.red * opt_bg_alpha) >> 16) + (65535 - opt_bg_alpha);
+ color.color.green = ((opt_bg_color.green * opt_bg_alpha) >> 16) + (65535 - opt_bg_alpha);
+ color.color.blue = ((opt_bg_color.blue * opt_bg_alpha) >> 16) + (65535 - opt_bg_alpha);
+ color.color.alpha = 65535;
+
+ XftDrawRect (draw, &color, 0, 0, *width, *height);
+
+ color.color.red = opt_fg_color.red;
+ color.color.blue = opt_fg_color.green;
+ color.color.green = opt_fg_color.blue;
+ color.color.alpha = opt_fg_alpha;
+
+ xft_context.draw = draw;
+ xft_context.color = color;
+
+ do_output (context, render_callback, NULL, &xft_context, state, width, height);
+
+ XftDrawDestroy (draw);
+}
+
+const PangoViewer pangoxft_viewer = {
+ "PangoXft",
+ "xft",
+ NULL,
+ pangoxft_view_create,
+ pangoxft_view_destroy,
+ pangoxft_view_get_context,
+ x_view_create_surface,
+ x_view_destroy_surface,
+ pangoxft_view_render,
+ NULL,
+ x_view_create_window,
+ x_view_destroy_window,
+ x_view_display
+};
diff --git a/utils/viewer-render.c b/utils/viewer-render.c
new file mode 100644
index 00000000..573cb59a
--- /dev/null
+++ b/utils/viewer-render.c
@@ -0,0 +1,823 @@
+/* viewer-render.c: Common code for rendering in viewers
+ *
+ * Copyright (C) 1999, 2004 Red Hat Software
+ * Copyright (C) 2001 Sun Microsystems
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include "config.h"
+#include <errno.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <pango/pango.h>
+
+#include "viewer-render.h"
+
+gboolean opt_display = TRUE;
+int opt_dpi = 96;
+gboolean opt_pixels = FALSE;
+const char *opt_font = "";
+gboolean opt_header = FALSE;
+const char *opt_output = NULL;
+int opt_margin_t = 10;
+int opt_margin_r = 10;
+int opt_margin_b = 10;
+int opt_margin_l = 10;
+int opt_markup = FALSE;
+gboolean opt_rtl = FALSE;
+double opt_rotate = 0;
+gboolean opt_auto_dir = TRUE;
+const char *opt_text = NULL;
+gboolean opt_waterfall = FALSE;
+int opt_width = -1;
+int opt_height = -1;
+int opt_indent = 0;
+gboolean opt_justify = 0;
+int opt_runs = 1;
+PangoAlignment opt_align = PANGO_ALIGN_LEFT;
+PangoEllipsizeMode opt_ellipsize = PANGO_ELLIPSIZE_NONE;
+PangoGravity opt_gravity = PANGO_GRAVITY_SOUTH;
+PangoGravityHint opt_gravity_hint = PANGO_GRAVITY_HINT_NATURAL;
+HintMode opt_hinting = HINT_DEFAULT;
+PangoWrapMode opt_wrap = PANGO_WRAP_WORD_CHAR;
+gboolean opt_wrap_set = FALSE;
+static const char *opt_pangorc = NULL; /* Unused */
+const PangoViewer *opt_viewer = NULL;
+const char *opt_language = NULL;
+gboolean opt_single_par = FALSE;
+PangoColor opt_fg_color = {0, 0, 0};
+guint16 opt_fg_alpha = 65535;
+gboolean opt_bg_set = FALSE;
+PangoColor opt_bg_color = {65535, 65535, 65535};
+guint16 opt_bg_alpha = 65535;
+
+/* Text (or markup) to render */
+static char *text;
+
+void
+fail (const char *format, ...)
+{
+ const char *msg;
+
+ va_list vap;
+ va_start (vap, format);
+ msg = g_strdup_vprintf (format, vap);
+ g_printerr ("%s: %s\n", g_get_prgname (), msg);
+
+ exit (1);
+}
+
+static PangoLayout *
+make_layout(PangoContext *context,
+ const char *text,
+ double size)
+{
+ static PangoFontDescription *font_description;
+ PangoAlignment align;
+ PangoLayout *layout;
+
+ layout = pango_layout_new (context);
+ if (opt_markup)
+ pango_layout_set_markup (layout, text, -1);
+ else
+ pango_layout_set_text (layout, text, -1);
+
+ pango_layout_set_auto_dir (layout, opt_auto_dir);
+ pango_layout_set_ellipsize (layout, opt_ellipsize);
+ pango_layout_set_justify (layout, opt_justify);
+ pango_layout_set_single_paragraph_mode (layout, opt_single_par);
+ pango_layout_set_wrap (layout, opt_wrap);
+
+ font_description = pango_font_description_from_string (opt_font);
+ if (size > 0)
+ pango_font_description_set_size (font_description, size * PANGO_SCALE);
+
+ if (opt_width > 0)
+ pango_layout_set_width (layout, (opt_width * opt_dpi * PANGO_SCALE + 36) / 72);
+
+ if (opt_height > 0)
+ pango_layout_set_height (layout, (opt_height * opt_dpi * PANGO_SCALE + 36) / 72);
+ else
+ pango_layout_set_height (layout, opt_height);
+
+ if (opt_indent != 0)
+ pango_layout_set_indent (layout, (opt_indent * opt_dpi * PANGO_SCALE + 36) / 72);
+
+ align = opt_align;
+ if (align != PANGO_ALIGN_CENTER &&
+ pango_context_get_base_dir (context) != PANGO_DIRECTION_LTR) {
+ /* pango reverses left and right if base dir ir rtl. so we should
+ * reverse to cancel that. unfortunately it also does that for
+ * rtl paragraphs, so we cannot really get left/right. all we get
+ * is default/other-side. */
+ align = PANGO_ALIGN_LEFT + PANGO_ALIGN_RIGHT - align;
+ }
+ pango_layout_set_alignment (layout, align);
+
+ pango_layout_set_font_description (layout, font_description);
+
+ pango_font_description_free (font_description);
+
+ return layout;
+}
+
+gchar *
+get_options_string (void)
+{
+ PangoFontDescription *font_description = pango_font_description_from_string (opt_font);
+ gchar *font_name;
+ gchar *result;
+
+ if (opt_waterfall)
+ pango_font_description_unset_fields (font_description, PANGO_FONT_MASK_SIZE);
+
+ font_name = pango_font_description_to_string (font_description);
+ result = g_strdup_printf ("%s: %s (%d dpi)", opt_viewer->name, font_name, opt_dpi);
+ pango_font_description_free (font_description);
+ g_free (font_name);
+
+ return result;
+}
+
+static void
+output_body (PangoLayout *layout,
+ RenderCallback render_cb,
+ gpointer cb_context,
+ gpointer cb_data,
+ int *width,
+ int *height,
+ gboolean supports_matrix)
+{
+ PangoRectangle logical_rect;
+ int size, start_size, end_size, increment;
+ int x = 0, y = 0;
+
+ if (!supports_matrix)
+ {
+ const PangoMatrix* matrix;
+ const PangoMatrix identity = PANGO_MATRIX_INIT;
+ PangoContext *context = pango_layout_get_context (layout);
+ matrix = pango_context_get_matrix (context);
+ if (matrix)
+ {
+ x += matrix->x0;
+ y += matrix->y0;
+ }
+ pango_context_set_matrix (context, &identity);
+ pango_layout_context_changed (layout);
+ }
+
+ if (opt_waterfall)
+ {
+ start_size = 8;
+ end_size = 48;
+ increment = 4;
+ }
+ else
+ {
+ start_size = end_size = -1;
+ increment = 1;
+ }
+
+ *width = 0;
+ *height = 0;
+
+ for (size = start_size; size <= end_size; size += increment)
+ {
+ if (size > 0)
+ {
+ PangoFontDescription *desc = pango_font_description_copy (pango_layout_get_font_description (layout));
+ pango_font_description_set_size (desc, size * PANGO_SCALE);
+ pango_layout_set_font_description (layout, desc);
+ pango_font_description_free (desc);
+ }
+
+ pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
+
+ if (render_cb)
+ (*render_cb) (layout, x, y+*height, cb_context, cb_data);
+
+ *width = MAX (*width,
+ MAX (logical_rect.x + logical_rect.width,
+ PANGO_PIXELS (pango_layout_get_width (layout))));
+ *height += MAX (logical_rect.y + logical_rect.height,
+ PANGO_PIXELS (pango_layout_get_height (layout)));
+ }
+}
+
+static void
+set_transform (PangoContext *context,
+ TransformCallback transform_cb,
+ gpointer cb_context,
+ gpointer cb_data,
+ PangoMatrix *matrix)
+{
+ pango_context_set_matrix (context, matrix);
+ if (transform_cb)
+ (*transform_cb) (context, matrix, cb_context, cb_data);
+}
+
+void
+do_output (PangoContext *context,
+ RenderCallback render_cb,
+ TransformCallback transform_cb,
+ gpointer cb_context,
+ gpointer cb_data,
+ int *width_out,
+ int *height_out)
+{
+ PangoLayout *layout;
+ PangoRectangle rect;
+ PangoMatrix matrix = PANGO_MATRIX_INIT;
+ PangoMatrix *orig_matrix;
+ gboolean supports_matrix;
+ int rotated_width, rotated_height;
+ int x = opt_margin_l;
+ int y = opt_margin_t;
+ int width, height;
+
+ width = 0;
+ height = 0;
+
+ orig_matrix = pango_matrix_copy (pango_context_get_matrix (context));
+ /* If the backend sets an all-zero matrix on the context,
+ * means that it doesn't support transformations.
+ */
+ supports_matrix = !orig_matrix ||
+ (orig_matrix->xx != 0. || orig_matrix->xy != 0. ||
+ orig_matrix->yx != 0. || orig_matrix->yy != 0. ||
+ orig_matrix->x0 != 0. || orig_matrix->y0 != 0.);
+
+ set_transform (context, transform_cb, cb_context, cb_data, NULL);
+
+ pango_context_set_language (context,
+ opt_language ? pango_language_from_string (opt_language)
+ : pango_language_get_default ());
+ pango_context_set_base_dir (context,
+ opt_rtl ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR);
+
+ if (opt_header)
+ {
+ char *options_string = get_options_string ();
+ pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
+ layout = make_layout (context, options_string, 10);
+ pango_layout_get_extents (layout, NULL, &rect);
+
+ width = MAX (width, PANGO_PIXELS (rect.width));
+ height += PANGO_PIXELS (rect.height);
+
+ if (render_cb)
+ (*render_cb) (layout, x, y, cb_context, cb_data);
+
+ y += PANGO_PIXELS (rect.height);
+
+ g_object_unref (layout);
+ g_free (options_string);
+ }
+
+ if (opt_rotate != 0)
+ {
+ if (supports_matrix)
+ pango_matrix_rotate (&matrix, opt_rotate);
+ else
+ g_printerr ("The backend does not support rotated text\n");
+ }
+
+ pango_context_set_base_gravity (context, opt_gravity);
+ pango_context_set_gravity_hint (context, opt_gravity_hint);
+
+ layout = make_layout (context, text, -1);
+
+ set_transform (context, transform_cb, cb_context, cb_data, &matrix);
+
+ output_body (layout,
+ NULL, NULL, NULL,
+ &rotated_width, &rotated_height,
+ supports_matrix);
+
+ rect.x = rect.y = 0;
+ rect.width = rotated_width;
+ rect.height = rotated_height;
+
+ pango_matrix_transform_pixel_rectangle (&matrix, &rect);
+
+ matrix.x0 = x - rect.x;
+ matrix.y0 = y - rect.y;
+
+ set_transform (context, transform_cb, cb_context, cb_data, &matrix);
+
+ if (render_cb)
+ output_body (layout,
+ render_cb, cb_context, cb_data,
+ &rotated_width, &rotated_height,
+ supports_matrix);
+
+ width = MAX (width, rect.width);
+ height += rect.height;
+
+ width += opt_margin_l + opt_margin_r;
+ height += opt_margin_t + opt_margin_b;
+
+ if (width_out)
+ *width_out = width;
+ if (height_out)
+ *height_out = height;
+
+ pango_context_set_matrix (context, orig_matrix);
+ pango_matrix_free (orig_matrix);
+ g_object_unref (layout);
+}
+
+static gboolean
+parse_enum (GType type,
+ int *value,
+ const char *name,
+ const char *arg,
+ gpointer data G_GNUC_UNUSED,
+ GError **error)
+{
+ char *possible_values = NULL;
+ gboolean ret;
+
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ ret = pango_parse_enum (type,
+ arg,
+ value,
+ FALSE,
+ &possible_values);
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+ if (!ret && error)
+ {
+ g_set_error(error,
+ G_OPTION_ERROR,
+ G_OPTION_ERROR_BAD_VALUE,
+ "Argument for %s must be one of %s",
+ name,
+ possible_values);
+ }
+
+ g_free (possible_values);
+
+ return ret;
+}
+
+static gboolean
+parse_align (const char *name,
+ const char *arg,
+ gpointer data,
+ GError **error)
+{
+ return parse_enum (PANGO_TYPE_ALIGNMENT, (int*)(void*)&opt_align,
+ name, arg, data, error);
+}
+
+static gboolean
+parse_ellipsis (const char *name,
+ const char *arg,
+ gpointer data,
+ GError **error)
+{
+ return parse_enum (PANGO_TYPE_ELLIPSIZE_MODE, (int*)(void*)&opt_ellipsize,
+ name, arg, data, error);
+}
+
+static gboolean
+parse_gravity (const char *name,
+ const char *arg,
+ gpointer data,
+ GError **error)
+{
+ return parse_enum (PANGO_TYPE_GRAVITY, (int*)(void*)&opt_gravity,
+ name, arg, data, error);
+}
+
+static gboolean
+parse_gravity_hint (const char *name,
+ const char *arg,
+ gpointer data,
+ GError **error)
+{
+ return parse_enum (PANGO_TYPE_GRAVITY_HINT, (int*)(void*)&opt_gravity_hint,
+ name, arg, data, error);
+}
+
+static gboolean
+parse_hinting (const char *name G_GNUC_UNUSED,
+ const char *arg,
+ gpointer data G_GNUC_UNUSED,
+ GError **error)
+{
+ gboolean ret = TRUE;
+
+ if (strcmp (arg, "none") == 0)
+ opt_hinting = HINT_NONE;
+ else if (strcmp (arg, "auto") == 0)
+ opt_hinting = HINT_AUTO;
+ else if (strcmp (arg, "full") == 0)
+ opt_hinting = HINT_FULL;
+ else
+ {
+ g_set_error(error,
+ G_OPTION_ERROR,
+ G_OPTION_ERROR_BAD_VALUE,
+ "Argument for --hinting must be one of none/auto/full");
+ ret = FALSE;
+ }
+
+ return ret;
+}
+
+static gboolean
+parse_wrap (const char *name,
+ const char *arg,
+ gpointer data,
+ GError **error)
+{
+ gboolean ret;
+ if ((ret = parse_enum (PANGO_TYPE_WRAP_MODE, (int*)(void*)&opt_wrap,
+ name, arg, data, error)))
+ {
+ opt_wrap_set = TRUE;
+ }
+ return ret;
+}
+
+static gboolean
+parse_rgba_color (PangoColor *color,
+ guint16 *alpha,
+ const char *name,
+ const char *arg,
+ gpointer data G_GNUC_UNUSED,
+ GError **error)
+{
+ gboolean ret;
+ char buf[32];
+ int len;
+
+ len = strlen (arg);
+ /* handle alpha */
+ if (*arg == '#' && (len == 5 || len == 9 || len == 17))
+ {
+ int width, bits;
+ unsigned int a;
+
+ bits = len - 1;
+ width = bits >> 2;
+
+ strcpy (buf, arg);
+ arg = buf;
+
+ if (!sscanf (buf + len - width, "%x", &a))
+ {
+ ret = FALSE;
+ goto err;
+ }
+ buf[len - width] = '\0';
+
+ a <<= (16 - bits);
+ while (bits < 16)
+ {
+ a |= (a >> bits);
+ bits *= 2;
+ }
+ *alpha = a;
+ }
+ else
+ *alpha = 65535;
+
+ ret = pango_color_parse (color, arg);
+
+err:
+ if (!ret && error)
+ {
+ g_set_error(error,
+ G_OPTION_ERROR,
+ G_OPTION_ERROR_BAD_VALUE,
+ "Argument for %s must be a color name like red, or CSS-style #rrggbb / #rrggbbaa",
+ name);
+ }
+
+ return ret;
+}
+
+static gboolean
+parse_foreground (const char *name,
+ const char *arg,
+ gpointer data,
+ GError **error)
+{
+ return parse_rgba_color (&opt_fg_color, &opt_fg_alpha,
+ name, arg, data, error);
+}
+
+static gboolean
+parse_background (const char *name,
+ const char *arg,
+ gpointer data,
+ GError **error)
+{
+ opt_bg_set = TRUE;
+
+ if (0 == strcmp ("transparent", arg))
+ {
+ opt_bg_alpha = 0;
+ return TRUE;
+ }
+
+ return parse_rgba_color (&opt_bg_color, &opt_bg_alpha,
+ name, arg, data, error);
+}
+
+static gboolean
+parse_margin (const char *name G_GNUC_UNUSED,
+ const char *arg,
+ gpointer data G_GNUC_UNUSED,
+ GError **error)
+{
+ switch (sscanf (arg, "%d%*[ ,]%d%*[ ,]%d%*[ ,]%d", &opt_margin_t, &opt_margin_r, &opt_margin_b, &opt_margin_l))
+ {
+ case 0:
+ {
+ g_set_error(error,
+ G_OPTION_ERROR,
+ G_OPTION_ERROR_BAD_VALUE,
+ "Argument for --margin must be one to four space-separated numbers");
+ return FALSE;
+ }
+ case 1: opt_margin_r = opt_margin_t;
+ case 2: opt_margin_b = opt_margin_t;
+ case 3: opt_margin_l = opt_margin_r;
+ }
+ return TRUE;
+}
+
+
+static gchar *
+backends_to_string (void)
+{
+ GString *backends = g_string_new (NULL);
+ const PangoViewer **viewer;
+
+ for (viewer = viewers; *viewer; viewer++)
+ if ((*viewer)->id)
+ {
+ g_string_append (backends, (*viewer)->id);
+ g_string_append_c (backends, '/');
+ }
+ g_string_truncate (backends, MAX (0, (gint)backends->len - 1));
+
+ return g_string_free(backends,FALSE);
+}
+
+static int
+backends_get_count (void)
+{
+ const PangoViewer **viewer;
+ int i = 0;
+
+ for (viewer = viewers; *viewer; viewer++)
+ if ((*viewer)->id)
+ i++;
+
+ return i;
+}
+
+
+static gchar *
+backend_description (void)
+{
+ GString *description = g_string_new("Pango backend to use for rendering ");
+ int backends_count = backends_get_count ();
+
+ if (backends_count > 1)
+ g_string_append_printf(description,"(default: %s)", (*viewers)->id);
+ else if (backends_count == 1)
+ g_string_append_printf(description,"(only available: %s)", (*viewers)->id);
+ else
+ g_string_append_printf(description,"(no backends found!)");
+
+ return g_string_free(description,FALSE);
+
+}
+
+static gboolean
+parse_backend (const char *name G_GNUC_UNUSED,
+ const char *arg,
+ gpointer data G_GNUC_UNUSED,
+ GError **error)
+{
+ gboolean ret = TRUE;
+ const PangoViewer **viewer;
+
+ for (viewer = viewers; *viewer; viewer++)
+ if (!g_ascii_strcasecmp ((*viewer)->id, arg))
+ break;
+
+ if (*viewer)
+ opt_viewer = *viewer;
+ else
+ {
+ gchar *backends = backends_to_string ();
+
+ g_set_error(error,
+ G_OPTION_ERROR,
+ G_OPTION_ERROR_BAD_VALUE,
+ "Available --backend options are: %s",
+ backends);
+ g_free(backends);
+ ret = FALSE;
+ }
+
+ return ret;
+}
+
+
+static G_GNUC_NORETURN gboolean
+show_version(const char *name G_GNUC_UNUSED,
+ const char *arg G_GNUC_UNUSED,
+ gpointer data G_GNUC_UNUSED,
+ GError **error G_GNUC_UNUSED)
+{
+ g_printf("%s (%s) %s\n", g_get_prgname (), PACKAGE_NAME, PACKAGE_VERSION);
+
+ if (PANGO_VERSION != pango_version())
+ g_printf("Linked Pango library has a different version: %s\n", pango_version_string ());
+
+ exit(0);
+}
+
+void
+parse_options (int argc, char *argv[])
+{
+ gchar *backend_options = backends_to_string ();
+ GOptionFlags backend_flag = backends_get_count () > 1 ? 0 : G_OPTION_FLAG_HIDDEN;
+ gchar *backend_desc = backend_description ();
+ GOptionEntry entries[] =
+ {
+ {"no-auto-dir", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_auto_dir,
+ "No layout direction according to contents", NULL},
+ {"backend", 0, backend_flag, G_OPTION_ARG_CALLBACK, &parse_backend,
+ backend_desc, backend_options},
+ {"background", 0, 0, G_OPTION_ARG_CALLBACK, &parse_background,
+ "Set the background color", "red/#rrggbb/#rrggbbaa/transparent"},
+ {"no-display", 'q', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_display,
+ "Do not display (just write to file or whatever)", NULL},
+ {"dpi", 0, 0, G_OPTION_ARG_INT, &opt_dpi,
+ "Set the resolution", "number"},
+ {"align", 0, 0, G_OPTION_ARG_CALLBACK, &parse_align,
+ "Text alignment", "left/center/right"},
+ {"ellipsize", 0, 0, G_OPTION_ARG_CALLBACK, &parse_ellipsis,
+ "Ellipsization mode", "start/middle/end"},
+ {"font", 0, 0, G_OPTION_ARG_STRING, &opt_font,
+ "Set the font description", "description"},
+ {"foreground", 0, 0, G_OPTION_ARG_CALLBACK, &parse_foreground,
+ "Set the text color", "red/#rrggbb/#rrggbbaa"},
+ {"gravity", 0, 0, G_OPTION_ARG_CALLBACK, &parse_gravity,
+ "Base gravity: glyph rotation", "south/east/north/west/auto"},
+ {"gravity-hint", 0, 0, G_OPTION_ARG_CALLBACK, &parse_gravity_hint,
+ "Gravity hint", "natural/strong/line"},
+ {"header", 0, 0, G_OPTION_ARG_NONE, &opt_header,
+ "Display the options in the output", NULL},
+ {"height", 0, 0, G_OPTION_ARG_INT, &opt_height,
+ "Height in points (positive) or number of lines (negative) for ellipsizing", "+points/-numlines"},
+ {"hinting", 0, 0, G_OPTION_ARG_CALLBACK, &parse_hinting,
+ "Hinting style", "none/auto/full"},
+ {"indent", 0, 0, G_OPTION_ARG_INT, &opt_indent,
+ "Width in points to indent paragraphs", "points"},
+ {"justify", 0, 0, G_OPTION_ARG_NONE, &opt_justify,
+ "Align paragraph lines to be justified", NULL},
+ {"language", 0, 0, G_OPTION_ARG_STRING, &opt_language,
+ "Language to use for font selection", "en_US/etc"},
+ {"margin", 0, 0, G_OPTION_ARG_CALLBACK, &parse_margin,
+ "Set the margin on the output in pixels", "CSS-style numbers in pixels"},
+ {"markup", 0, 0, G_OPTION_ARG_NONE, &opt_markup,
+ "Interpret text as Pango markup", NULL},
+ {"output", 'o', 0, G_OPTION_ARG_STRING, &opt_output,
+ "Save rendered image to output file", "file"},
+ {"pangorc", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &opt_pangorc,
+ "Deprecated", "file"},
+ {"pixels", 0, 0, G_OPTION_ARG_NONE, &opt_pixels,
+ "Use pixel units instead of points (sets dpi to 72)", NULL},
+ {"rtl", 0, 0, G_OPTION_ARG_NONE, &opt_rtl,
+ "Set base direction to right-to-left", NULL},
+ {"rotate", 0, 0, G_OPTION_ARG_DOUBLE, &opt_rotate,
+ "Angle at which to rotate results", "degrees"},
+ {"runs", 'n', 0, G_OPTION_ARG_INT, &opt_runs,
+ "Run Pango layout engine this many times", "integer"},
+ {"single-par", 0, 0, G_OPTION_ARG_NONE, &opt_single_par,
+ "Enable single-paragraph mode", NULL},
+ {"text", 't', 0, G_OPTION_ARG_STRING, &opt_text,
+ "Text to display (instead of a file)", "string"},
+ {"version", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, &show_version,
+ "Show version numbers", NULL},
+ {"waterfall", 0, 0, G_OPTION_ARG_NONE, &opt_waterfall,
+ "Create a waterfall display", NULL},
+ {"width", 'w', 0, G_OPTION_ARG_INT, &opt_width,
+ "Width in points to which to wrap lines or ellipsize", "points"},
+ {"wrap", 0, 0, G_OPTION_ARG_CALLBACK, &parse_wrap,
+ "Text wrapping mode (needs a width to be set)", "word/char/word-char"},
+ {NULL}
+ };
+ GError *error = NULL;
+ GError *parse_error = NULL;
+ GOptionContext *context;
+ size_t len;
+ const PangoViewer **viewer;
+
+ context = g_option_context_new ("- FILE");
+ g_option_context_add_main_entries (context, entries, NULL);
+
+ for (viewer = viewers; *viewer; viewer++)
+ if ((*viewer)->get_option_group)
+ {
+ GOptionGroup *group = (*viewer)->get_option_group (*viewer);
+ if (group)
+ g_option_context_add_group (context, group);
+ }
+
+ if (!g_option_context_parse (context, &argc, &argv, &parse_error))
+ {
+ if (parse_error != NULL)
+ fail("%s", parse_error->message);
+ else
+ fail("Option parse error");
+ exit(1);
+ }
+ g_option_context_free(context);
+ g_free(backend_options);
+ g_free(backend_desc);
+
+ if (opt_pixels)
+ opt_dpi = 72;
+
+ if ((opt_text && argc != 1) || (!opt_text && argc != 2))
+ {
+ if (opt_text && argc != 1)
+ fail ("When specifying --text, no file should be given");
+
+ g_printerr ("Usage: %s [OPTION...] FILE\n", g_get_prgname ());
+ exit (1);
+ }
+
+ /* set up the backend */
+ if (!opt_viewer)
+ {
+ opt_viewer = *viewers;
+ if (!opt_viewer)
+ fail ("No viewer backend found");
+ }
+
+ /* Get the text
+ */
+ if (opt_text)
+ {
+ text = g_strdup (opt_text);
+ len = strlen (text);
+ }
+ else
+ {
+ if (!g_file_get_contents (argv[1], &text, &len, &error))
+ fail ("%s\n", error->message);
+ }
+
+ /* Strip one trailing newline
+ */
+ if (len > 0 && text[len - 1] == '\n')
+ len--;
+ if (len > 0 && text[len - 1] == '\r')
+ len--;
+ text[len] = '\0';
+
+ /* Make sure we have valid markup
+ */
+ if (opt_markup &&
+ !pango_parse_markup (text, -1, 0, NULL, NULL, NULL, &error))
+ fail ("Cannot parse input as markup: %s", error->message);
+}
+
+
+void
+finalize (void)
+{
+ g_free (text);
+}
diff --git a/utils/viewer-render.h b/utils/viewer-render.h
new file mode 100644
index 00000000..ade22986
--- /dev/null
+++ b/utils/viewer-render.h
@@ -0,0 +1,90 @@
+/* viewer-render.c: Common code for rendering in viewers
+ *
+ * Copyright (C) 1999, 2004 Red Hat Software
+ * Copyright (C) 2001 Sun Microsystems
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef VIEWER_RENDER_H
+#define VIEWER_RENDER_H
+
+#include <pango/pango-layout.h>
+
+#include "viewer.h"
+
+typedef enum {
+ HINT_DEFAULT,
+ HINT_NONE,
+ HINT_AUTO,
+ HINT_FULL
+} HintMode;
+
+typedef void (*RenderCallback) (PangoLayout *layout,
+ int x,
+ int y,
+ gpointer cb_context,
+ gpointer cb_data);
+typedef void (*TransformCallback) (PangoContext *context,
+ PangoMatrix *transform,
+ gpointer cb_context,
+ gpointer cb_data);
+
+void fail (const char *format, ...) G_GNUC_PRINTF (1, 2) G_GNUC_NORETURN;
+
+void parse_options (int argc,
+ char *argv[]);
+void do_output (PangoContext *context,
+ RenderCallback render_cb,
+ TransformCallback transform_cb,
+ gpointer cb_context,
+ gpointer cb_data,
+ int *width,
+ int *height);
+void finalize (void);
+gchar *get_options_string (void);
+
+extern const char *prog_name;
+
+/* handled by viewer-render.c */
+extern const char *opt_font;
+extern gboolean opt_header;
+extern int opt_margin;
+extern int opt_markup;
+extern gboolean opt_rtl;
+extern double opt_rotate;
+extern gboolean opt_auto_dir;
+extern const char *opt_text;
+extern gboolean opt_waterfall;
+extern int opt_width;
+extern int opt_indent;
+extern PangoEllipsizeMode opt_ellipsize;
+
+/* handled by viewer-main.c */
+extern gboolean opt_display;
+extern const char *opt_output;
+extern int opt_runs;
+extern const PangoViewer *opt_viewer;
+
+/* handled by backend-specific code */
+extern int opt_dpi;
+extern HintMode opt_hinting;
+extern PangoColor opt_fg_color;
+extern guint16 opt_fg_alpha;
+extern gboolean opt_bg_set;
+extern PangoColor opt_bg_color;
+extern guint16 opt_bg_alpha;
+
+#endif /* VIEWER_RENDER_H */
diff --git a/utils/viewer-x.c b/utils/viewer-x.c
new file mode 100644
index 00000000..ad1c9a8d
--- /dev/null
+++ b/utils/viewer-x.c
@@ -0,0 +1,234 @@
+/* viewer-x.c: Common code for X-based viewers
+ *
+ * Copyright (C) 1999,2004,2005 Red Hat, Inc.
+ * Copyright (C) 2001 Sun Microsystems
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include "config.h"
+#include <string.h>
+
+#include "viewer-render.h"
+#include "viewer-x.h"
+
+void
+x_view_init (gpointer instance,
+ const PangoViewer *klass G_GNUC_UNUSED)
+{
+ XViewer *x = (XViewer *)instance;
+
+ x->display = XOpenDisplay (NULL);
+ if (!x->display)
+ fail ("Cannot open display %s", XDisplayName (NULL));
+
+ x->screen = DefaultScreen (x->display);
+}
+
+gpointer
+x_view_create (const PangoViewer *klass)
+{
+ XViewer *instance;
+
+ instance = g_slice_new (XViewer);
+
+ x_view_init (instance, klass);
+
+ return instance;
+}
+
+void
+x_view_destroy (gpointer instance)
+{
+ XViewer *x = (XViewer *)instance;
+
+ XCloseDisplay (x->display);
+
+ g_slice_free (XViewer, instance);
+}
+
+gpointer
+x_view_create_surface (gpointer instance,
+ int width,
+ int height)
+{
+ XViewer *x = (XViewer *) instance;
+ Pixmap pixmap;
+
+ pixmap = XCreatePixmap (x->display, DefaultRootWindow (x->display), width, height,
+ DefaultDepth (x->display, x->screen));
+
+ return (gpointer) pixmap;
+}
+
+void
+x_view_destroy_surface (gpointer instance,
+ gpointer surface)
+{
+ XViewer *x = (XViewer *) instance;
+ Pixmap pixmap = (Pixmap) surface;
+
+ XFreePixmap (x->display, pixmap);
+}
+
+static void
+update (Display *display,
+ Pixmap pixmap,
+ Window window,
+ Region *update_region)
+{
+ GC gc;
+ XRectangle extents;
+
+ XClipBox (*update_region, &extents);
+
+ gc = XCreateGC (display, pixmap, 0, NULL);
+
+ XCopyArea (display, pixmap, window, gc,
+ extents.x, extents.y,
+ extents.width, extents.height,
+ extents.x, extents.y);
+
+ XFreeGC (display, gc);
+
+ XDestroyRegion (*update_region);
+ *update_region = NULL;
+}
+
+static void
+expose (XExposeEvent *xev,
+ Region *update_region)
+{
+ XRectangle r;
+
+ if (!*update_region)
+ *update_region = XCreateRegion ();
+
+ r.x = xev->x;
+ r.y = xev->y;
+ r.width = xev->width;
+ r.height = xev->height;
+
+ XUnionRectWithRegion (&r, *update_region, *update_region);
+}
+
+gpointer
+x_view_create_window (gpointer instance,
+ const char *title,
+ int width,
+ int height)
+{
+ XViewer *x = (XViewer *) instance;
+ unsigned long bg;
+ Window window;
+ XSizeHints size_hints;
+
+ bg = WhitePixel (x->display, x->screen);
+ window = XCreateSimpleWindow (x->display, DefaultRootWindow (x->display),
+ 0, 0, width, height, 0,
+ bg, bg);
+
+ XSelectInput (x->display, window, ExposureMask | KeyPressMask);
+
+ XMapWindow (x->display, window);
+ XmbSetWMProperties (x->display, window,
+ title,
+ NULL, NULL, 0, NULL, NULL, NULL);
+
+ memset ((char *)&size_hints, 0, sizeof (XSizeHints));
+ size_hints.flags = PSize | PMaxSize;
+ size_hints.width = width; size_hints.height = height; /* for compat only */
+ size_hints.max_width = width; size_hints.max_height = height;
+
+ XSetWMNormalHints (x->display, window, &size_hints);
+
+ return (gpointer) window;
+}
+
+void
+x_view_destroy_window (gpointer instance,
+ gpointer window)
+{
+ XViewer *x = (XViewer *) instance;
+ Window win = (Window) window;
+
+ XDestroyWindow (x->display, win);
+}
+
+gpointer
+x_view_display (gpointer instance,
+ gpointer surface,
+ gpointer win,
+ int width,
+ int height,
+ gpointer state)
+{
+ XViewer *x = (XViewer *) instance;
+ Pixmap pixmap = (Pixmap) surface;
+ Window window = (Window) win;
+ XEvent xev;
+ XRectangle r;
+ Region update_region;
+ unsigned int quit_keycode;
+ unsigned int annotate_keycode;
+
+ /* force a full redraw */
+ update_region = XCreateRegion ();
+ r.x = 0;
+ r.y = 0;
+ r.width = width;
+ r.height = height;
+ XUnionRectWithRegion (&r, update_region, update_region);
+
+ annotate_keycode = XKeysymToKeycode(x->display, 'B');
+ quit_keycode = XKeysymToKeycode(x->display, 'Q');
+
+ while (1)
+ {
+ if (!XPending (x->display) && update_region)
+ update (x->display, pixmap, window, &update_region);
+
+ XNextEvent (x->display, &xev);
+ switch (xev.xany.type) {
+ case KeyPress:
+ if (xev.xkey.keycode == quit_keycode)
+ return GINT_TO_POINTER (-1);
+ else if (xev.xkey.keycode == annotate_keycode)
+ {
+ return GUINT_TO_POINTER (GPOINTER_TO_INT (state) + 1);
+ }
+ break;
+ case Expose:
+ expose (&xev.xexpose, &update_region);
+ break;
+ }
+ }
+}
+
+const PangoViewer x_viewer = {
+ "X",
+ NULL,
+ NULL,
+ x_view_create,
+ x_view_destroy,
+ NULL,
+ x_view_create_surface,
+ x_view_destroy_surface,
+ NULL,
+ NULL,
+ x_view_create_window,
+ x_view_destroy_window,
+ x_view_display
+};
diff --git a/utils/viewer-x.h b/utils/viewer-x.h
new file mode 100644
index 00000000..28f61f6b
--- /dev/null
+++ b/utils/viewer-x.h
@@ -0,0 +1,70 @@
+/* viewer-x.h: Common headers for X-based viewers
+ *
+ * Copyright (C) 1999,2004,2005 Red Hat, Inc.
+ * Copyright (C) 2001 Sun Microsystems
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef VIEWER_X_H
+#define VIEWER_X_H
+
+#include <pango/pango.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include "viewer.h"
+
+
+typedef struct
+{
+ Display *display;
+ int screen;
+} XViewer;
+
+
+extern const PangoViewer x_viewer;
+
+void x_view_init (gpointer instance,
+ const PangoViewer *klass);
+
+gpointer x_view_create (const PangoViewer *klass);
+
+void x_view_destroy (gpointer instance);
+
+gpointer x_view_create_surface (gpointer instance,
+ int width,
+ int height);
+
+void x_view_destroy_surface (gpointer instance,
+ gpointer surface);
+
+gpointer x_view_create_window (gpointer instance,
+ const char *title,
+ int width,
+ int height);
+
+void x_view_destroy_window (gpointer instance,
+ gpointer window);
+
+gpointer x_view_display (gpointer instance,
+ gpointer surface,
+ gpointer window,
+ int width,
+ int height,
+ gpointer state);
+
+#endif /* VIEWER_X_H */
diff --git a/utils/viewer.h b/utils/viewer.h
new file mode 100644
index 00000000..a4937693
--- /dev/null
+++ b/utils/viewer.h
@@ -0,0 +1,99 @@
+/* viewer.h: PangoViewer class
+ *
+ * Copyright (C) 1999,2004,2005 Red Hat, Inc.
+ * Copyright (C) 2001 Sun Microsystems
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef VIEWER_H
+#define VIEWER_H
+
+#include <stdio.h>
+#include <pango/pango.h>
+
+typedef struct _PangoViewer PangoViewer;
+
+struct _PangoViewer {
+
+ const char *name;
+
+ const char *id;
+
+ const char *write_suffix;
+
+ gpointer (*create) (const PangoViewer *klass);
+
+ void (*destroy) (gpointer instance);
+
+ PangoContext * (*get_context) (gpointer instance);
+
+ gpointer (*create_surface) (gpointer instance,
+ int width,
+ int height);
+
+ void (*destroy_surface) (gpointer instance,
+ gpointer surface);
+
+ void (*render) (gpointer instance,
+ gpointer surface,
+ PangoContext *context,
+ int *width,
+ int *height,
+ gpointer state);
+
+ /* The following can be NULL */
+
+ void (*write) (gpointer instance,
+ gpointer surface,
+ FILE *stream,
+ int width,
+ int height);
+
+ gpointer (*create_window) (gpointer instance,
+ const char *title,
+ int width,
+ int height);
+
+ void (*destroy_window) (gpointer instance,
+ gpointer window);
+
+ gpointer (*display) (gpointer instance,
+ gpointer surface,
+ gpointer window,
+ int width,
+ int height,
+ gpointer state);
+
+ void (*load) (gpointer instance,
+ gpointer surface,
+ guchar *buffer,
+ int width,
+ int height,
+ int stride);
+
+ void (*save) (gpointer instance,
+ gpointer surface,
+ guchar *buffer,
+ int width,
+ int height,
+ int stride);
+
+ GOptionGroup * (*get_option_group) (const PangoViewer *klass);
+};
+
+extern const PangoViewer *viewers[];
+
+#endif /* VIEWER_H */