PDF में टेक्स्ट एडिटिंग कैसे काम करती है: कंटेंट स्ट्रीम, फ़ॉन्ट एन्कोडिंग और एक बार में सिर्फ़ एक पंक्ति क्यों
PDF के पेज में पैराग्राफ़ या टेक्स्ट बॉक्स नहीं होते, केवल ऐसे निर्देश होते हैं जो किसी फ़ॉन्ट से किसी स्थान पर ग्लिफ़ बनाते हैं। एक पंक्ति बदलने का मतलब पुराने निर्देश हटाकर उसी जगह नए निर्देश लिखना है, और यही तय करता है कि क्या बदला जा सकता है और क्या नहीं।

PDF के पेज में न पैराग्राफ़ होते हैं, न टेक्स्ट बॉक्स। उसमें निर्देशों की एक सूची होती है: इस स्थान पर, इस फ़ॉन्ट और इस आकार में, ये ग्लिफ़ बनाओ। टेक्स्ट बदलने का मतलब उस पंक्ति को बनाने वाले निर्देश हटाना और उसी स्थान पर, उसी फ़ॉन्ट, आकार और रंग के साथ नए निर्देश लिखना है। इसीलिए PDF संपादित करें एक बार में एक पंक्ति बदलता है और टेक्स्ट को रैप नहीं कर सकता: यह इस बात से निकलता है कि टेक्स्ट फ़ाइल में कैसे रखा जाता है।
फ़ाइल में टेक्स्ट की एक पंक्ति कैसी दिखती है
पेज की सामग्री कंटेंट स्ट्रीम में रहती है, जो ऑपरेटरों से बना एक छोटा प्रोग्राम है। टेक्स्ट के ज़्यादातर काम चार ऑपरेटर करते हैं: BT और ET टेक्स्ट के एक ब्लॉक को घेरते हैं, Tf फ़ॉन्ट और आकार चुनता है, Td अगले स्थान पर जाता है, और Tj एक स्ट्रिंग बनाता है। स्टैंडर्ड फ़ॉन्ट के साथ सबसे सरल रूप ऐसा दिखता है:
BT /F1 18 Tf 20 150 Td (Hello World) Tj ET
इसका अर्थ है: फ़ॉन्ट F1 को 18 पॉइंट पर चुनो, x 20, y 150 पर जाओ, और Hello World बनाओ। Chrome से प्रिंट की गई PDF आमतौर पर अलग दिखती है: कोष्ठकों में अब अक्षर नहीं, फ़ॉन्ट के ग्लिफ़ नंबर होते हैं, और Tm स्थान एक बार तय कर देता है। नीचे Chrome से एक्सपोर्ट किए गए पेज की पंक्ति Hello World from Georgia है, जिसमें हर चार हेक्साडेसिमल अंक एक ग्लिफ़ नंबर हैं:
BT /F4 24 Tf 1 0 0 -1 30 52 Tm <002b0048004f004f00520003003a00520055004f0047000300490055005200500003002a004800520055004a004c0044> Tj ET
002b H है, 0048 e है, 004f l है, 0003 स्पेस है, 003a W है, और आगे इसी तरह। ये संख्याएँ केवल इसी एक फ़ॉन्ट के भीतर मायने रखती हैं; ये Unicode नहीं हैं। जब आप टेक्स्ट कॉपी करते हैं, तो व्यूअर फ़ाइल में कहीं और रखी लुकअप तालिका से इन संख्याओं को वापस अक्षरों में बदलता है।
इससे दो बातें निकलती हैं। पहली, कंटेंट स्ट्रीम में कहीं नहीं लिखा होता कि "यह एक पैराग्राफ़ है": पंक्ति-विराम बस एक और Td या Tm है जो स्थान को अगली पंक्ति पर ले जाता है, और एक पैराग्राफ़ में कितनी पंक्तियाँ हैं यह स्थानों से अनुमान लगाना पड़ता है। दूसरी, पंक्ति में कोई नया अक्षर लिखने के लिए आपको उस फ़ॉन्ट में उस अक्षर की संख्या चाहिए, और फ़ॉन्ट में अक्सर वही ग्लिफ़ होते हैं जो फ़ाइल लिखते समय इस्तेमाल हुए थे। यह फ़ॉन्ट सबसेटिंग का नतीजा है।
आप qpdf से स्ट्रीम खोल सकते हैं:
qpdf --qdf --object-streams=disable in.pdf out.pdf
out.pdf को टेक्स्ट एडिटर में खोलें और Tj खोजें, तो पेज पर बनाई गई हर स्ट्रिंग दिख जाएगी। PDF में और क्या-क्या होता है, इसके लिए देखें PDF के भीतर क्या है: ऑब्जेक्ट, स्ट्रीम और क्रॉस-रेफ़रेंस तालिका।
एडिटर ग्लिफ़ से "पंक्ति" कैसे दोबारा बनाता है
एडिटर को पहले बिखरे ग्लिफ़ को ऐसी पंक्ति में समूहित करना होता है जिस पर उपयोगकर्ता क्लिक कर सके। जिन ग्लिफ़ का फ़ॉन्ट, आकार और रंग एक ही है और जिनकी पिछले ग्लिफ़ से दूरी एक फ़ॉन्ट आकार से ज़्यादा नहीं है, वे एक ही टुकड़े के हैं; पंक्ति-विराम, नया फ़ॉन्ट या नया रंग टुकड़े को समाप्त कर देता है। (यहाँ टुकड़ा यानी एक ही फ़ॉन्ट, आकार और रंग के सटे हुए ग्लिफ़ का क्रम।) जो टेक्स्ट दाएँ से बाएँ या ऊर्ध्वाधर चलता है, उसे समूहित नहीं किया जाता और वह संपादन के लिए खुलता नहीं।
दो नतीजे सीधे देखे जा सकते हैं। दो फ़ॉन्ट वाली पंक्ति, जैसे चीनी पंक्ति जिसमें एक अंग्रेज़ी शब्द हो, दो आसन्न टुकड़ों में बँट जाती है जिन पर अलग-अलग क्लिक होता है। संपादन के बाद भी यही होता है: अगर नया टेक्स्ट दूसरे फ़ॉन्ट का है, तो पंक्ति दो टुकड़े बन जाती है, और एक को बदलने पर दूसरा जस का तस रहता है।
एक पंक्ति बदलना: पहले पुराना टेक्स्ट हटाना, फिर नया लिखना
हटाने में पाठ काला करने का वह रूप इस्तेमाल होता है जो केवल टेक्स्ट पर लागू होता है: पंक्ति के क्षेत्र के भीतर के ग्लिफ़ कंटेंट स्ट्रीम से हटा दिए जाते हैं, इमेज और रेखाएँ वैसी ही रहती हैं, और पुराने टेक्स्ट के ऊपर कोई रंगीन बॉक्स नहीं बनाया जाता, इसलिए कॉपी और खोज अब उसे नहीं पाते। यह केवल पेज की सामग्री बदलता है; अगर वही टेक्स्ट किसी एनोटेशन या दस्तावेज़ के गुणों में भी है, तो वे प्रभावित नहीं होते।
लिखना पेज की कंटेंट स्ट्रीम के अंत में नया टेक्स्ट निर्देश जोड़ता है, जो मूल पंक्ति की बेसलाइन के स्थान से शुरू होता है और उसका आकार और रंग दोबारा इस्तेमाल करता है। ऊपर की पंक्ति को Hello World 2 करने के बाद पेज के अंत में यह जोड़ा जाता है:
q 1 0 0 1 22.5 148.92 cm 0 0 0 rg
/Span <</ActualText <feff00480065006c006c006f00200057006f0072006c006400200032>>> BDC
BT /F4 18 Tf 0 0 Td <002b0048004f004f00520003003a00520055004f00470003> Tj ET
BT /F9 18 Tf 102.18 0 Td (2) Tj ET
EMC
Q
पहला BT मूल फ़ॉन्ट F4 और मूल ग्लिफ़ नंबर इस्तेमाल करता है, इसलिए Hello World ठीक वैसा ही दोबारा इस्तेमाल हो जाता है। "2" मूल फ़ॉन्ट सबसेट में नहीं है, इसलिए दूसरा BT उसे बनाने के लिए एक नया स्टैंडर्ड Helvetica फ़ॉन्ट (F9) शुरू करता है। q और Q इस ब्लॉक का निर्देशांक रूपांतरण और रंग उसी तक सीमित रखते हैं; एडिटर मूल सामग्री को भी q/Q की जोड़ी में लपेटता है, ताकि मूल पेज के अंत में असंतुलित छूटी स्थिति नए टेक्स्ट पर असर न डाले। यह एक टुकड़े का रूप है; संख्याएँ फ़ाइल के हिसाब से बदलती हैं।
अंत में जोड़ने के तीन नतीजे हैं:
- नया टेक्स्ट सबसे आख़िर में बनाया जाता है, पेज की बाक़ी हर चीज़ के ऊपर। जो टेक्स्ट पहले किसी इमेज या रंगीन ब्लॉक के नीचे था, वह संपादन के बाद उसके ऊपर दिखता है।
- कंटेंट-स्ट्रीम क्रम में टेक्स्ट निकालने वाला टूल (जैसे
pdftotext -raw) उस पेज पर संपादित पंक्ति को सबसे आख़िर में पढ़ता है, जबकि स्थान के आधार पर निकालना (pdftotext -layout) कोई बदलाव नहीं दिखाता। पेज वैसा ही दिखता है, पर पढ़ने का क्रम बदल गया है। - एडिटर नए टेक्स्ट के साथ ActualText जोड़ता है, जो व्यूअर को बताता है कि ग्लिफ़ असल में किन अक्षरों के लिए हैं। एक ग्लिफ़ कई अक्षरों से जुड़ सकता है, और केवल ग्लिफ़ नंबर से खोजने पर कभी-कभी संगतता वाला अक्षर मिलता है (जैसे 文 की जगह कांग्शी रेडिकल ⽂ मिलना); ActualText पक्का करता है कि कॉपी करने पर वही मिले जो आपने टाइप किया।
नया टेक्स्ट कौन-सा फ़ॉन्ट बनाता है
मूल फ़ॉन्ट दोबारा इस्तेमाल करना सबसे सुरक्षित है, क्योंकि ग्लिफ़ की आकृति, मोटाई और चौड़ाई पास के टेक्स्ट से मेल खाती है। यह दोबारा इस्तेमाल हो सकता है या नहीं, यह इस पर निर्भर है कि एडिटर यह जान पाता है या नहीं कि उस फ़ॉन्ट में किसी अक्षर का कोड क्या है:
- ऊपर जैसा संयुक्त फ़ॉन्ट (Type0, दो-बाइट ग्लिफ़ नंबर, Identity-H एन्कोडिंग): अक्षर को फ़ॉन्ट में खोजकर उसका ग्लिफ़ नंबर लें, और मिल जाए तो उसे इस्तेमाल करें। न मिले, तो वह अक्षर सबसेट में नहीं है।
- WinAnsi या MacRoman एन्कोडिंग वाला एक-बाइट सरल फ़ॉन्ट (TrueType, Type1): अक्षर से बाइट की मैपिंग तय है, और चौड़ाई फ़ाइल की चौड़ाई तालिका से आती है।
- अनुकूलित एन्कोडिंग (Differences) वाला फ़ॉन्ट, या वह जो फ़ॉन्ट की अपनी अंतर्निहित एन्कोडिंग पर निर्भर है: कोई भरोसेमंद मैपिंग नहीं है, इसलिए एडिटर अंदाज़ा नहीं लगाता और फ़ॉलबैक फ़ॉन्ट पर जाता है, वरना टेक्स्ट में ग़लत अक्षर आ जाएँगे।
जब कोई अक्षर मूल फ़ॉन्ट में नहीं होता, तो फ़ॉलबैक फ़ॉन्ट इस्तेमाल होता है। सादे लैटिन अक्षर Helvetica, Times और Courier में से सबसे निकट वाले का इस्तेमाल करते हैं, जो हर व्यूअर के पास मौजूद स्टैंडर्ड फ़ॉन्ट हैं, इसलिए कुछ एम्बेड करने की ज़रूरत नहीं पड़ती। बाक़ी सब, जैसे चीनी अक्षर, काना, हंगुल, थाई, उच्चारण-चिह्न वाले लैटिन अक्षर, सिरिलिक और ग्रीक, ओपन-सोर्स Noto फ़ॉन्ट इस्तेमाल करते हैं: सर्वर अनुरोध के अक्षर लेता है और ओपन-सोर्स HarfBuzz सबसेटिंग लाइब्रेरी से पूरे फ़ॉन्ट में से बस वही ग्लिफ़ काट लेता है, आमतौर पर कुछ KB, और ब्राउज़र उन्हें आपकी फ़ाइल में एम्बेड कर देता है। चीनी अक्षर का क्षेत्रीय रूप इंटरफ़ेस भाषा के अनुसार चलता है: सरलीकृत, पारंपरिक, जापानी या कोरियाई।
सिर्फ़ फ़ॉन्ट का नाम लिखने के बजाय ग्लिफ़ एम्बेड किए जाते हैं, क्योंकि केवल नाम से संदर्भित फ़ॉन्ट की जगह हर व्यूअर अपना चुना हुआ फ़ॉलबैक लगा देता है और मोटाई व्यूअर-दर-व्यूअर अलग हो सकती है; एम्बेड होने पर हर व्यूअर वही आकृतियाँ दिखाता है।
एक बार में सिर्फ़ एक पंक्ति क्यों
एडिटर में कोई लेआउट इंजन नहीं है। फ़ाइल में हर ग्लिफ़ की स्थिति तय है; नया टेक्स्ट बस शुरुआती बिंदु से फ़ॉन्ट की चौड़ाइयों के आधार पर दाईं ओर बिछाया जाता है, "किनारे पर रैप करो" जैसा कोई तर्क नहीं है, और उसके बाद की कोई चीज़ खिसकाई नहीं जाती। पंक्ति लंबी करें तो अतिरिक्त अक्षर दाईं ओर चलते जाते हैं, और जो पेज के किनारे से आगे निकल जाए वह दिखता नहीं। पैराग्राफ़ दोबारा लिखने या तालिका में पंक्तियाँ जोड़ने के लिए नया लेआउट चाहिए, यानी उस सोर्स फ़ाइल पर लौटना जिससे PDF बनी थी।
चौड़ाई भी केवल फ़ॉन्ट की अपनी एडवांस चौड़ाई (हर ग्लिफ़ के बाद लिखने का बिंदु कितना दाईं ओर खिसकता है) है, बिना कर्निंग और बिना ग्लिफ़ प्रतिस्थापन के। जिस टेक्स्ट को शेपिंग (संदर्भ के अनुसार ग्लिफ़ का रूप और स्थान तय करना) नहीं चाहिए (लैटिन, सिरिलिक, ग्रीक और चीनी अक्षर) वह सही बिछता है। थाई टोन-चिह्नों को सही जगह बैठने के लिए ग्लिफ़ पोज़िशनिंग चाहिए, जो एडिटर नहीं करता, इसलिए वे खिसके हुए हो सकते हैं। अरबी को संदर्भ पर निर्भर ग्लिफ़ रूप और दाएँ से बाएँ लेआउट चाहिए, हिब्रू को दाएँ से बाएँ लेआउट चाहिए, और भारतीय लिपियों में व्यंजन और मात्राओं को मिलाकर ग्लिफ़ बनाने पड़ते हैं; इन सबके लिए पूरी टेक्स्ट शेपिंग चाहिए, जो एडिटर अभी नहीं करता, इसलिए इन्हें टाइप करना ग़लत अक्षर बनाने के बजाय अस्वीकार कर दिया जाता है।
दो और सीमाएँ फ़ाइल से ही आती हैं। स्कैन किए पेज की सामग्री एक ही इमेज होती है जिसमें कोई टेक्स्ट ऑपरेटर नहीं होता, इसलिए हटाने या बदलने को कुछ नहीं होता। डिजिटल हस्ताक्षर वाली फ़ाइल संपादित करने के बाद, हस्ताक्षर जिस मूल सामग्री की रक्षा करता था वह बदल चुकी होती है, और हस्ताक्षर मान्य नहीं रहता।
ख़ुद जाँचें
ऊपर वर्णित बदलाव आप डाउनलोड की गई फ़ाइल में तीन कमांड से देख सकते हैं:
pdftotext -raw edited.pdf - # कंटेंट-स्ट्रीम क्रम में निकालें: पुराना टेक्स्ट ग़ायब है, संपादित पंक्ति सबसे आख़िर में आती है
pdffonts edited.pdf # फ़ॉन्ट सूची: दोबारा इस्तेमाल हुए मूल फ़ॉन्ट, और नया एम्बेड किया गया फ़ॉन्ट सबसेट
qpdf --qdf --object-streams=disable edited.pdf edited.qdf # कंटेंट स्ट्रीम खोलें; जोड़ा गया BT ... ET ब्लॉक अंत में होता है
आगे पढ़ें
- फ़ॉन्ट सबसेटिंग संपादन क्यों तोड़ती है, पढ़ना क्यों नहीं: मूल फ़ॉन्ट में कोई अक्षर क्यों नहीं हो सकता, और तब क्या होता है।
- PDF के भीतर क्या है: ऑब्जेक्ट, स्ट्रीम और क्रॉस-रेफ़रेंस तालिका: कंटेंट स्ट्रीम, ऑब्जेक्ट और फ़ाइल संरचना की पूरी तस्वीर।
- PDF में टेक्स्ट ऑनलाइन बदलें: सोर्स फ़ाइल के बिना सुधार: इसे कैसे इस्तेमाल करें, यह किसके लिए ठीक है, और सोर्स फ़ाइल पर कब लौटना चाहिए।