Android पेंट: .measureText () बनाम .getTextBounds ()


191

मैं पाठ का उपयोग कर Paint.getTextBounds()रहा हूं, क्योंकि मुझे पाठ की ऊंचाई और चौड़ाई दोनों प्रदान करने में रुचि है। हालाँकि, प्रदान किया गया वास्तविक पाठ हमेशा भरी हुई जानकारी .width()की तुलना में थोड़ा व्यापक होता Rectहै getTextBounds()

मेरे आश्चर्य के लिए, मैंने परीक्षण किया .measureText(), और पाया कि यह एक अलग (उच्च) मान लौटाता है। मैंने इसे एक कोशिश दी, और इसे सही पाया।

वे अलग-अलग चौड़ाई की रिपोर्ट क्यों करते हैं? मैं सही ढंग से ऊंचाई और चौड़ाई कैसे प्राप्त कर सकता हूं? मेरा मतलब है, मैं उपयोग कर सकता हूं .measureText(), लेकिन तब मुझे नहीं पता होगा कि क्या मुझे उसके .height()द्वारा दिए गए भरोसे पर भरोसा करना चाहिए getTextBounds()

अनुरोध के अनुसार, समस्या को पुन: पेश करने के लिए यहां न्यूनतम कोड है:

final String someText = "Hello. I believe I'm some text!";

Paint p = new Paint();
Rect bounds = new Rect();

for (float f = 10; f < 40; f += 1f) {
    p.setTextSize(f);

    p.getTextBounds(someText, 0, someText.length(), bounds);

    Log.d("Test", String.format(
        "Size %f, measureText %f, getTextBounds %d",
        f,
        p.measureText(someText),
        bounds.width())
    );
}

आउटपुट से पता चलता है कि अंतर न केवल 1 से अधिक हो जाता है (और अंतिम-मिनट की राउंडिंग त्रुटि नहीं है), बल्कि आकार के साथ बढ़ना भी प्रतीत होता है (मैं अधिक निष्कर्ष निकालने वाला था, लेकिन यह पूरी तरह से फ़ॉन्ट-निर्भर हो सकता है):

D/Test    (  607): Size 10.000000, measureText 135.000000, getTextBounds 134
D/Test    (  607): Size 11.000000, measureText 149.000000, getTextBounds 148
D/Test    (  607): Size 12.000000, measureText 156.000000, getTextBounds 155
D/Test    (  607): Size 13.000000, measureText 171.000000, getTextBounds 169
D/Test    (  607): Size 14.000000, measureText 195.000000, getTextBounds 193
D/Test    (  607): Size 15.000000, measureText 201.000000, getTextBounds 199
D/Test    (  607): Size 16.000000, measureText 211.000000, getTextBounds 210
D/Test    (  607): Size 17.000000, measureText 225.000000, getTextBounds 223
D/Test    (  607): Size 18.000000, measureText 245.000000, getTextBounds 243
D/Test    (  607): Size 19.000000, measureText 251.000000, getTextBounds 249
D/Test    (  607): Size 20.000000, measureText 269.000000, getTextBounds 267
D/Test    (  607): Size 21.000000, measureText 275.000000, getTextBounds 272
D/Test    (  607): Size 22.000000, measureText 297.000000, getTextBounds 294
D/Test    (  607): Size 23.000000, measureText 305.000000, getTextBounds 302
D/Test    (  607): Size 24.000000, measureText 319.000000, getTextBounds 316
D/Test    (  607): Size 25.000000, measureText 330.000000, getTextBounds 326
D/Test    (  607): Size 26.000000, measureText 349.000000, getTextBounds 346
D/Test    (  607): Size 27.000000, measureText 357.000000, getTextBounds 354
D/Test    (  607): Size 28.000000, measureText 369.000000, getTextBounds 365
D/Test    (  607): Size 29.000000, measureText 396.000000, getTextBounds 392
D/Test    (  607): Size 30.000000, measureText 401.000000, getTextBounds 397
D/Test    (  607): Size 31.000000, measureText 418.000000, getTextBounds 414
D/Test    (  607): Size 32.000000, measureText 423.000000, getTextBounds 418
D/Test    (  607): Size 33.000000, measureText 446.000000, getTextBounds 441
D/Test    (  607): Size 34.000000, measureText 455.000000, getTextBounds 450
D/Test    (  607): Size 35.000000, measureText 468.000000, getTextBounds 463
D/Test    (  607): Size 36.000000, measureText 474.000000, getTextBounds 469
D/Test    (  607): Size 37.000000, measureText 500.000000, getTextBounds 495
D/Test    (  607): Size 38.000000, measureText 506.000000, getTextBounds 501
D/Test    (  607): Size 39.000000, measureText 521.000000, getTextBounds 515

आप [मेरे रास्ते] [१] देख सकते हैं। यह स्थिति और सही बाध्य हो सकता है। [1]: stackoverflow.com/a/19979937/1621354
एलेक्स ची

जवाबों:


370

आप ऐसी समस्या का निरीक्षण करने के लिए मैंने क्या किया:

Android स्रोत कोड, Paint.java स्रोत का अध्ययन करें, दोनों को देखे। आप यह जानेंगे कि मापक टेक्स्ट native_measureText कहता है, और getTextBounds nativeGetStringBounds को कॉल करता है, जो C ++ में लागू किए गए मूल तरीके हैं।

तो आप Paint.cpp का अध्ययन करना जारी रखेंगे, जो दोनों को लागू करता है।

native_measureText -> SkPaintGlue :: उपायText_CII

nativeGetStringBounds -> SkPaintGlue :: getStringBounds

अब आपका अध्ययन बताता है कि ये विधियाँ कहाँ भिन्न हैं। कुछ पैराम चेक के बाद, दोनों स्केप लिब में मापक कार्य करते हैं: स्किया लिब (एंड्रॉइड का हिस्सा) में मापक, लेकिन वे दोनों अलग-अलग अतिभारित रूप में कॉल करते हैं।

स्किया में और खुदाई करते हुए, मैं देखता हूं कि दोनों कॉल एक ही फ़ंक्शन में एक ही संगणना में परिणाम करते हैं, केवल परिणाम को अलग तरीके से लौटाते हैं।

आपके प्रश्न का उत्तर देने के लिए: आपकी दोनों कॉल समान संगणना करती हैं। परिणाम का संभावित अंतर वास्तव में निहित है कि getTextBounds पूर्णांक के रूप में सीमा देता है, जबकि मापटैक्स रिटर्न फ्लोट मूल्य है।

तो जो आपको मिलता है वह है फ़्लोटिंग को इंट में रूपांतरण के दौरान गोल करने में त्रुटि, और यह Sk.caintGlue :: doTextBounds में कॉल करने के लिए SkRect :: RoundOut को कॉल करने के लिए Paint.cpp में होता है।

उन दो कॉलों की गणना की गई चौड़ाई के बीच का अंतर अधिकतम 1 हो सकता है।

EDIT 4 अक्टूबर 2011

विज़ुअलाइज़ेशन से बेहतर क्या हो सकता है। मैंने प्रयास किया, स्वयं की खोज के लिए, और योग्य वर के लिए :)

यहां छवि विवरण दर्ज करें

यह फ़ॉन्ट आकार 60 है, लाल रंग में सीमा आयत है, बैंगनी में इसका माप है।

यह देखा गया है कि बायां भाग बाईं ओर से कुछ पिक्सल शुरू होता है, और माप का मान बाईं और दाईं ओर इस मान से बढ़ा हुआ है। इसे ग्लिफ़ की एडवांसएक्स वैल्यू कहा जाता है। (मैंने SkPaint.cpp में Skia स्रोतों में इसकी खोज की है)

इसलिए परीक्षण का परिणाम यह है कि मापक पाठ में दोनों तरफ के पाठ के लिए कुछ अग्रिम मूल्य जोड़ता है, जबकि getTextBounds न्यूनतम सीमा की गणना करता है जहां दिया गया पाठ फिट होगा।

आशा है कि यह परिणाम आपके लिए उपयोगी है।

परीक्षण कोड:

  protected void onDraw(Canvas canvas){
     final String s = "Hello. I'm some text!";

     Paint p = new Paint();
     Rect bounds = new Rect();
     p.setTextSize(60);

     p.getTextBounds(s, 0, s.length(), bounds);
     float mt = p.measureText(s);
     int bw = bounds.width();

     Log.i("LCG", String.format(
          "measureText %f, getTextBounds %d (%s)",
          mt,
          bw, bounds.toShortString())
      );
     bounds.offset(0, -bounds.top);
     p.setStyle(Style.STROKE);
     canvas.drawColor(0xff000080);
     p.setColor(0xffff0000);
     canvas.drawRect(bounds, p);
     p.setColor(0xff00ff00);
     canvas.drawText(s, 0, bounds.bottom, p);
  }

3
टिप्पणी के लिए इतना समय लेने के लिए क्षमा करें, मेरे पास एक व्यस्त सप्ताह था। C ++ कोड में खुदाई करने के लिए समय निकालने के लिए धन्यवाद! दुर्भाग्य से, अंतर 1 से अधिक है, और जब गोल मानों का उपयोग यह भी होता है -। लिए measureText में 29.0 परिणाम () = 263 और getTextBounds () जैसे सेटिंग पाठ का आकार चौड़ाई = 260
slezica

कृपया पेंट सेटअप और माप के साथ न्यूनतम कोड पोस्ट करें, जो समस्या को पुन: उत्पन्न कर सकता है।
सूचक31

अपडेट किया गया। देरी के लिए फिर से क्षमा करें।
स्लीज़िका

मैं यहां रिच के साथ हूं। महान शोध! बाउंटी पूरी तरह से योग्य और सम्मानित किया गया। आपके समय और कोशिश के लिए धन्यवाद!
स्लीज़िका

16
@ मैं केवल इस प्रश्न को फिर से पाया (मैं ओपी हूँ)। मैं भूल गया था कि आपका उत्तर कितना अविश्वसनीय था। भविष्य से धन्यवाद! सर्वश्रेष्ठ 100rp मैंने निवेश किया है!
स्लीज़िका

21

इसके साथ मेरा अनुभव यह है कि getTextBoundsपाठ को इनकैप्स करने वाले उस निरपेक्ष न्यूनतम बाउंडिंग रेक को वापस करेंगे, जबकि रेंडरिंग के दौरान उपयोग की जाने वाली माप की गई चौड़ाई जरूरी नहीं है। मैं यह भी कहना चाहता हूं कि measureTextएक लाइन मान लें।

सटीक माप परिणाम प्राप्त करने के लिए, आपको StaticLayoutपाठ को रेंडर करने और माप को बाहर निकालने के लिए उपयोग करना चाहिए ।

उदाहरण के लिए:

String text = "text";
TextPaint textPaint = textView.getPaint();
int boundedWidth = 1000;

StaticLayout layout = new StaticLayout(text, textPaint, boundedWidth , Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
int height = layout.getHeight();

StaticLayout के बारे में पता नहीं था! getWidth () काम नहीं करेगा, यह सिर्फ वही लौटाता है जो मैं कॉस्ट्रोक्टर में पास करता हूं (जो है width, नहीं maxWidth), लेकिन getLineWith () होगा। मुझे स्टैटिक मेथड getDesiredWidth () भी मिला, हालाँकि कोई हाइट बराबर नहीं है। कृपया उत्तर सही करें! इसके अलावा, मैं वास्तव में यह जानना चाहूंगा कि इतने सारे तरीके अलग-अलग परिणाम क्यों देते हैं।
स्लीज़िका

चौड़ाई के बारे में मेरा बुरा। यदि आप एक पंक्ति में प्रस्तुत किए जाने वाले कुछ पाठ की चौड़ाई चाहते हैं, तो measureTextठीक काम करना चाहिए। अन्यथा, यदि आप चौड़ाई में कमी जानते हैं (जैसे कि onMeasureया तो onLayout, आप मेरे द्वारा पोस्ट किए गए समाधान का उपयोग कर सकते हैं। क्या आप पाठ प्रतिरूप का आकार बदलने की कोशिश कर रहे हैं? इसके अलावा, कारण पाठ सीमा हमेशा थोड़ी छोटी होती है, क्योंकि यह किसी भी वर्ण अवरोधन को बाहर करती है। , ड्राइंग के लिए आवश्यक सबसे छोटी बंधी हुई जड़।
चेस

मैं वास्तव में एक TextView को ऑटो-आकार देने की कोशिश कर रहा हूं। मुझे ऊंचाई की भी आवश्यकता है, बस यहीं से मेरी समस्याएं शुरू हुईं! मापक () अन्यथा ठीक काम करता है। उसके बाद, मैं उत्सुक हो गया कि अलग-अलग दृष्टिकोणों ने अलग-अलग मूल्य क्यों दिए हैं
स्लीज़िका

1
एक बात यह भी ध्यान दें कि एक टेक्स्टव्यू जो लपेटता है और एक बहुस्तरीय है जो चौड़ाई के लिए match_parent पर मापेगा न कि केवल आवश्यक चौड़ाई के लिए, जो उपरोक्त चौड़ाई को StaticLayout को मान्य बनाता है। यदि आपको एक पाठ पुनर्विक्रेता की आवश्यकता है, तो मेरी पोस्ट बाहर की जाँच करें stackoverflow.com/questions/5033012/… पर
चेस

मुझे एक पाठ विस्तार एनीमेशन की आवश्यकता थी और इसने एक गुच्छा में मदद की! धन्यवाद!
बॉक्स

18

चूहों का जवाब बहुत अच्छा है ... और यहाँ असली समस्या का वर्णन है:

संक्षिप्त सरल उत्तर यह है कि Paint.getTextBounds(String text, int start, int end, Rect bounds)रिटर्न Rectजो शुरू नहीं होता है (0,0)। यही है, टेक्स्ट की वास्तविक चौड़ाई प्राप्त करने के लिए जो गेटटेक्स्टबाउंड्स () से एक ही पेंट ऑब्जेक्ट के Canvas.drawText(String text, float x, float y, Paint paint)साथ कॉल करके सेट किया जाएगा, आपको रीक्ट की बाईं स्थिति को जोड़ना चाहिए। ऐसा कुछ:

public int getTextWidth(String text, Paint paint) {
    Rect bounds = new Rect();
    paint.getTextBounds(text, 0, end, bounds);
    int width = bounds.left + bounds.width();
    return width;
}

इस पर ध्यान दें bounds.left- यह समस्या की कुंजी है।

इस तरह आप पाठ की उसी चौड़ाई को प्राप्त करेंगे, जिसे आप उपयोग करते हुए प्राप्त करेंगे Canvas.drawText()

और heightपाठ पाने के लिए समान कार्य होना चाहिए :

public int getTextHeight(String text, Paint paint) {
    Rect bounds = new Rect();
    paint.getTextBounds(text, 0, end, bounds);
    int height = bounds.bottom + bounds.height();
    return height;
}

Ps: मैंने इस सटीक कोड का परीक्षण नहीं किया, लेकिन गर्भाधान का परीक्षण किया।


इस उत्तर में बहुत अधिक विस्तृत विवरण दिया गया है ।


2
(B.right-b.left) और (b.height) को (b.bottom-b.top) होने के लिए Rect परिभाषित करता है। इसलिए आप (b.left + b.width) को (b.right) और (b.bottom + b.height) को (2 * b.bottom-b.top) से बदल सकते हैं, लेकिन मुझे लगता है कि आप चाहते हैं ( b.bottom-b.top) के बजाय, जो (b.height) के समान है। मुझे लगता है कि आप चौड़ाई के बारे में सही हैं (C ++ स्रोत की जाँच के आधार पर), getTextWidth () (b.right) के समान मान लौटाता है, हो सकता है कि कुछ राउंडिंग त्रुटियाँ हों। (b सीमा के संदर्भ में है)
Nulano

11

उस सवाल पर फिर से जवाब देने के लिए क्षमा करें ... मुझे छवि को एम्बेड करने की आवश्यकता है।

मुझे लगता है कि मिले हुए परिणाम @ मिस मिस हो रहे हैं। अवलोकन 60 के फ़ॉन्ट आकार के लिए सही हो सकते हैं, लेकिन पाठ के छोटे होने पर वे अधिक भिन्न हो जाते हैं। उदाहरण के लिए। 10px। उस मामले में पाठ वास्तव में सीमा से परे है।

यहां छवि विवरण दर्ज करें

स्क्रीनशॉट का सोर्सकोड:

  @Override
  protected void onDraw( Canvas canvas ) {
    for( int i = 0; i < 20; i++ ) {
      int startSize = 10;
      int curSize = i + startSize;
      paint.setTextSize( curSize );
      String text = i + startSize + " - " + TEXT_SNIPPET;
      Rect bounds = new Rect();
      paint.getTextBounds( text, 0, text.length(), bounds );
      float top = STEP_DISTANCE * i + curSize;
      bounds.top += top;
      bounds.bottom += top;
      canvas.drawRect( bounds, bgPaint );
      canvas.drawText( text, 0, STEP_DISTANCE * i + curSize, paint );
    }
  }

आह, मिस्टर पर चला जाता है। मैं अभी भी इस में रुचि रखता हूं, भले ही जिज्ञासा से बाहर हो। मुझे पता है अगर तुम कुछ और पता करो, कृपया!
स्लीज़िका

समस्या को तब थोड़ा दूर किया जा सकता है जब आप 20 से कहे जाने वाले फोंट को गुणा करके आप उस परिणाम को मापना चाहते हैं और उसके द्वारा परिणाम को विभाजित करें। हो सकता है कि आप भी मापा आकार के 1-2% अधिक चौड़ाई को केवल सुरक्षित पक्ष पर जोड़ना चाहें। अंत में आपके पास वह पाठ होगा जो लंबे समय तक मापा जाता है लेकिन कम से कम कोई पाठ अतिव्यापी नहीं होता है।
मोरिट्ज़

6
टेक्स्ट को बाउंडिंग रेक्ट के बिल्कुल अंदर खींचने के लिए आपको X = 0.0f के साथ टेक्‍स्‍ट ड्रा नहीं करना चाहिए, क्‍योंकि बाउंडिंग रेक्ट रेक्ट की तरह वापस आ रहा है, न कि चौड़ाई और ऊंचाई की तरह। इसे सही ढंग से खींचने के लिए आपको "-bounds.left" मान पर X समन्वय करना चाहिए, फिर इसे सही ढंग से दिखाया जाएगा।
रोमन

10

अस्वीकरण: न्यूनतम चौड़ाई निर्धारित करने के मामले में यह समाधान 100% सटीक नहीं है।

मैं यह भी पता लगा रहा था कि कैसे एक कैनवास पर पाठ को मापना है। चूहों से महान पोस्ट को पढ़ने के बाद मुझे बहुस्तरीय पाठ को मापने के तरीके पर कुछ समस्याएं थीं। इन योगदानों का कोई स्पष्ट तरीका नहीं है, लेकिन कुछ शोध के बाद मैं स्टैटिकलैटआउट वर्ग में कैम। यह आपको बहुस्तरीय पाठ ("\ n" के साथ पाठ) को मापने और संबंधित पेंट के माध्यम से अपने पाठ के बहुत अधिक गुणों को कॉन्फ़िगर करने की अनुमति देता है।

यहाँ एक स्निपेट दिखाया गया है कि बहु-पाठ को कैसे मापें:

private StaticLayout measure( TextPaint textPaint, String text, Integer wrapWidth ) {
    int boundedWidth = Integer.MAX_VALUE;
    if (wrapWidth != null && wrapWidth > 0 ) {
       boundedWidth = wrapWidth;
    }
    StaticLayout layout = new StaticLayout( text, textPaint, boundedWidth, Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false );
    return layout;
}

यदि आप अपने बहुस्तरीय पाठ को एक निश्चित चौड़ाई तक सीमित करना चाहते हैं, तो इससे रेकविट को निर्धारित किया जा सकता है।

चूंकि StaticLayout.getWidth () केवल इस सीमा को वापस करता है, इसलिए आपको अपने बहुस्तरीय पाठ द्वारा आवश्यक अधिकतम चौड़ाई प्राप्त करने के लिए एक और कदम उठाना होगा। आप प्रत्येक लाइनों की चौड़ाई निर्धारित करने में सक्षम हैं और अधिकतम चौड़ाई पाठ्यक्रम की उच्चतम लाइन चौड़ाई है:

private float getMaxLineWidth( StaticLayout layout ) {
    float maxLine = 0.0f;
    int lineCount = layout.getLineCount();
    for( int i = 0; i < lineCount; i++ ) {
        if( layout.getLineWidth( i ) > maxLine ) {
            maxLine = layout.getLineWidth( i );
        }
    }
    return maxLine;
}

क्या आप igetLineWidth में नहीं जा रहे हैं (आप हर बार 0 पास कर रहे हैं)
Rich S

2

टेक्स्ट सीमा को ठीक से मापने का एक और तरीका है, पहले आपको वर्तमान पेंट और टेक्स्ट के लिए पथ प्राप्त करना चाहिए। आपके मामले में यह इस तरह होना चाहिए:

p.getTextPath(someText, 0, someText.length(), 0.0f, 0.0f, mPath);

उसके बाद आप कॉल कर सकते हैं:

mPath.computeBounds(mBoundsPath, true);

मेरे कोड में यह हमेशा सही और अपेक्षित मान देता है। लेकिन, यह सुनिश्चित नहीं है कि यह आपके दृष्टिकोण से अधिक तेजी से काम करता है।


जब से मैं पाठ के चारों ओर एक स्ट्रोक की रूपरेखा तैयार करने के लिए, मैं वैसे भी getTextPath कर रहा था, मुझे यह उपयोगी लगा।
ToolmakerSteve

1

नीचे दी गई छवि के बीच का अंतर getTextBoundsऔर measureTextवर्णन किया गया है।

संक्षेप में,

  1. getTextBoundsसटीक पाठ का RECT प्राप्त करना है। measureTextपाठ की लंबाई, बाईं और दाईं तरफ अतिरिक्त खाई भी शामिल है।

  2. यदि पाठ के बीच रिक्त स्थान हैं, तो इसे मापा जाता है, measureTextलेकिन पाठ सीमा की लंबाई में शामिल नहीं है, हालांकि समन्वयन स्थानांतरित हो जाता है।

  3. पाठ को तिरछा (तिरछा) छोड़ा जा सकता है। इस स्थिति में, बायीं ओर का बाउंडिंग बॉक्स मापक के माप से अधिक होगा, और बाउंड की कुल लंबाई की तुलना में बड़ा होगाmeasureText

  4. पाठ सही (तिरछा) झुकाया जा सकता है। इस स्थिति में, बाउंडिंग बॉक्स दाईं ओर के मापक के माप से अधिक होगा, और बाध्य की गई पाठ की समग्र लंबाई से बड़ी होगीmeasureText

यहां छवि विवरण दर्ज करें


0

इस तरह मैंने पहले अक्षर के लिए वास्तविक आयामों की गणना की (आप अपनी आवश्यकताओं के अनुरूप विधि शीर्षलेख को बदल सकते हैं, अर्थात char[]उपयोग के बजाय String):

private void calculateTextSize(char[] text, PointF outSize) {
    // use measureText to calculate width
    float width = mPaint.measureText(text, 0, 1);

    // use height from getTextBounds()
    Rect textBounds = new Rect();
    mPaint.getTextBounds(text, 0, 1, textBounds);
    float height = textBounds.height();
    outSize.x = width;
    outSize.y = height;
}

ध्यान दें कि मैं मूल पेंट क्लास के बजाय TextPaint का उपयोग कर रहा हूं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.