@@ -129,6 +129,14 @@ private slots:
129129 void embeddedImageLineHeight ();
130130 void unmatchedShapedSubstring ();
131131 void maximumLayoutWidthInWrappedLayout ();
132+ void standaloneInlineObject_lineHeight ();
133+ void standaloneInlineObject_verticalAlignment ();
134+ #ifdef QT_BUILD_INTERNAL
135+ void standaloneInlineObject_cursorToX_data ();
136+ void standaloneInlineObject_cursorToX ();
137+ void standaloneInlineObject_wrapping ();
138+ void standaloneInlineObject_eliding ();
139+ #endif
132140
133141private:
134142 QFont testFont;
@@ -2873,5 +2881,228 @@ void tst_QTextLayout::maximumLayoutWidthInWrappedLayout()
28732881 QCOMPARE (reference.maximumWidth (), breakByWidth.maximumWidth ());
28742882}
28752883
2884+ // Helper: build a standalone QTextLayout with ObjectReplacementCharacter
2885+ // at each position listed in |objectPositions|, sized via QTextImageFormat.
2886+
2887+ static constexpr qreal TEST_OBJECT_WIDTH = 32 ;
2888+
2889+ static void setupStandaloneLayoutWithObjects (QTextLayout &layout, const QString &text,
2890+ const QFont &font, const QList<int > &objectPositions,
2891+ qreal objWidth, qreal objHeight)
2892+ {
2893+ layout.setText (text);
2894+ layout.setFont (font);
2895+
2896+ QList<QTextLayout::FormatRange> formats;
2897+ for (int pos : objectPositions) {
2898+ Q_ASSERT (text.at (pos) == QChar::ObjectReplacementCharacter);
2899+ QTextLayout::FormatRange range;
2900+ QTextImageFormat imgFmt;
2901+ imgFmt.setWidth (objWidth);
2902+ imgFmt.setHeight (objHeight);
2903+ range.format = imgFmt;
2904+ range.start = pos;
2905+ range.length = 1 ;
2906+ formats.append (range);
2907+ }
2908+ layout.setFormats (formats);
2909+ }
2910+
2911+ void tst_QTextLayout::standaloneInlineObject_lineHeight ()
2912+ {
2913+ // Not data-driven because expected values depend on testFont metrics
2914+ // which are only available after initTestCase() loads the test font.
2915+ const QChar orc = QChar::ObjectReplacementCharacter;
2916+ QString text = QStringLiteral (" X" ) + orc + QStringLiteral (" Y" );
2917+
2918+ // Reference: plain text line metrics (no inline object).
2919+ QTextLayout refLayout (QStringLiteral (" XY" ), testFont);
2920+ refLayout.beginLayout ();
2921+ QTextLine refLine = refLayout.createLine ();
2922+ refLine.setLineWidth (1000 );
2923+ refLayout.endLayout ();
2924+
2925+ const qreal fontHeight = refLine.height ();
2926+ const qreal fontDescent = refLine.descent ();
2927+
2928+ auto testCase = [&](qreal objHeight, qreal expectedHeight) {
2929+ QTextLayout layout;
2930+ setupStandaloneLayoutWithObjects (layout, text, testFont, { 1 }, 20 , objHeight);
2931+ layout.beginLayout ();
2932+ QTextLine line = layout.createLine ();
2933+ line.setLineWidth (1000 );
2934+ layout.endLayout ();
2935+
2936+ QCOMPARE (line.height (), expectedHeight);
2937+ QCOMPARE (line.descent (), fontDescent);
2938+ };
2939+
2940+ // Large object (taller than font) — line height equals object height.
2941+ testCase (80 , 80 );
2942+ // Font-sized object — line height equals font height.
2943+ testCase (TESTFONT_SIZE , fontHeight);
2944+ // Small object (shorter than font) — line height equals font height.
2945+ testCase (TESTFONT_SIZE / 4 , fontHeight);
2946+ }
2947+
2948+ void tst_QTextLayout::standaloneInlineObject_verticalAlignment ()
2949+ {
2950+ // Verify that the three vertical alignment modes from
2951+ // QTextDocumentLayout::resizeInlineObject() are supported.
2952+ const QChar orc = QChar::ObjectReplacementCharacter;
2953+ const qreal objHeight = 80 ;
2954+ const qreal objWidth = 20 ;
2955+ QString text = QStringLiteral (" X" ) + orc + QStringLiteral (" Y" );
2956+
2957+ auto layoutWithAlignment = [&](QTextCharFormat::VerticalAlignment align) {
2958+ QTextLayout layout;
2959+ layout.setText (text);
2960+ layout.setFont (testFont);
2961+
2962+ QTextLayout::FormatRange range;
2963+ QTextImageFormat imgFmt;
2964+ imgFmt.setWidth (objWidth);
2965+ imgFmt.setHeight (objHeight);
2966+ imgFmt.setVerticalAlignment (align);
2967+ range.format = imgFmt;
2968+ range.start = 1 ;
2969+ range.length = 1 ;
2970+ layout.setFormats ({ range });
2971+
2972+ layout.beginLayout ();
2973+ QTextLine line = layout.createLine ();
2974+ line.setLineWidth (1000 );
2975+ layout.endLayout ();
2976+ return std::make_pair (line.ascent (), line.descent ());
2977+ };
2978+
2979+ // Default: descent=0, ascent=h → baseline at bottom of image.
2980+ {
2981+ auto [ascent, descent] = layoutWithAlignment (QTextCharFormat::AlignNormal);
2982+ QCOMPARE (ascent, objHeight);
2983+ QCOMPARE (descent, qreal (0 ));
2984+ }
2985+
2986+ // AlignBaseline: descent=fm.descent(), ascent=h-descent.
2987+ {
2988+ const QFontMetricsF fm (testFont);
2989+ auto [ascent, descent] = layoutWithAlignment (QTextCharFormat::AlignBaseline);
2990+ QCOMPARE (descent, fm.descent ());
2991+ QCOMPARE (ascent, objHeight - fm.descent ());
2992+ }
2993+
2994+ // AlignMiddle: centered on x-height/2.
2995+ {
2996+ const QFontMetricsF fm (testFont);
2997+ const qreal halfX = fm.xHeight () / 2.0 ;
2998+ auto [ascent, descent] = layoutWithAlignment (QTextCharFormat::AlignMiddle);
2999+ QCOMPARE (ascent, (objHeight + halfX) / 2.0 );
3000+ QCOMPARE (descent, (objHeight - halfX) / 2.0 );
3001+ }
3002+ }
3003+
3004+ #ifdef QT_BUILD_INTERNAL
3005+ void tst_QTextLayout::standaloneInlineObject_cursorToX_data ()
3006+ {
3007+ QTest::addColumn<QString>(" text" );
3008+ QTest::addColumn<QList<int >>(" objectPositions" );
3009+ QTest::addColumn<int >(" cursorPos" );
3010+ QTest::addColumn<qreal>(" expectedX" );
3011+
3012+ const QChar orc = QChar::ObjectReplacementCharacter;
3013+
3014+ // Single object: "A<ORC>B"
3015+ const QString single = QStringLiteral (" A" ) + orc + QStringLiteral (" B" );
3016+ QTest::newRow (" single: before text" ) << single << QList<int >{1 } << 0 << qreal (0 );
3017+ QTest::newRow (" single: before object" ) << single << QList<int >{1 } << 1 << qreal (TESTFONT_SIZE );
3018+ QTest::newRow (" single: after object" ) << single << QList<int >{1 } << 2 << qreal (TESTFONT_SIZE + TEST_OBJECT_WIDTH );
3019+
3020+ // Multiple adjacent objects: "A<ORC><ORC>B"
3021+ const QString multi = QStringLiteral (" A" ) + orc + orc + QStringLiteral (" B" );
3022+ QTest::newRow (" multi: before first object" ) << multi << QList<int >{1 , 2 } << 1 << qreal (TESTFONT_SIZE );
3023+ QTest::newRow (" multi: between objects" ) << multi << QList<int >{1 , 2 } << 2 << qreal (TESTFONT_SIZE + TEST_OBJECT_WIDTH );
3024+ QTest::newRow (" multi: after second object" ) << multi << QList<int >{1 , 2 } << 3 << qreal (TESTFONT_SIZE + TEST_OBJECT_WIDTH * 2 );
3025+
3026+ // Object at start: "<ORC>B"
3027+ const QString atStart = QString (orc) + QStringLiteral (" B" );
3028+ QTest::newRow (" start: before object" ) << atStart << QList<int >{0 } << 0 << qreal (0 );
3029+ QTest::newRow (" start: after object" ) << atStart << QList<int >{0 } << 1 << qreal (TEST_OBJECT_WIDTH );
3030+
3031+ // Object at end: "A<ORC>"
3032+ const QString atEnd = QStringLiteral (" A" ) + orc;
3033+ QTest::newRow (" end: before object" ) << atEnd << QList<int >{1 } << 1 << qreal (TESTFONT_SIZE );
3034+ QTest::newRow (" end: after object" ) << atEnd << QList<int >{1 } << 2 << qreal (TESTFONT_SIZE + TEST_OBJECT_WIDTH );
3035+ }
3036+
3037+ void tst_QTextLayout::standaloneInlineObject_cursorToX ()
3038+ {
3039+ QFETCH (QString, text);
3040+ QFETCH (QList<int >, objectPositions);
3041+ QFETCH (int , cursorPos);
3042+ QFETCH (qreal, expectedX);
3043+
3044+ QTextLayout layout;
3045+ setupStandaloneLayoutWithObjects (layout, text, testFont, objectPositions,
3046+ TEST_OBJECT_WIDTH , TEST_OBJECT_WIDTH );
3047+ layout.beginLayout ();
3048+ QTextLine line = layout.createLine ();
3049+ line.setLineWidth (1000 );
3050+ layout.endLayout ();
3051+
3052+ QCOMPARE (line.cursorToX (cursorPos), expectedX);
3053+ }
3054+
3055+ void tst_QTextLayout::standaloneInlineObject_wrapping ()
3056+ {
3057+ // "<ORC>A" — object is 3*TESTFONT_SIZE wide, line width fits the object
3058+ // but not object + 'A', so 'A' must wrap to the second line.
3059+ const QChar orc = QChar::ObjectReplacementCharacter;
3060+ const qreal objWidth = 3 * TESTFONT_SIZE ;
3061+ QString text = QString (orc) + QStringLiteral (" A" );
3062+
3063+ QTextLayout layout;
3064+ setupStandaloneLayoutWithObjects (layout, text, testFont, { 0 }, objWidth, 10 );
3065+
3066+ QTextOption opt;
3067+ opt.setWrapMode (QTextOption::WrapAnywhere);
3068+ layout.setTextOption (opt);
3069+
3070+ layout.beginLayout ();
3071+ QTextLine line1 = layout.createLine ();
3072+ line1.setLineWidth (3.5 * TESTFONT_SIZE );
3073+ QTextLine line2 = layout.createLine ();
3074+ if (line2.isValid ())
3075+ line2.setLineWidth (3.5 * TESTFONT_SIZE );
3076+ layout.endLayout ();
3077+
3078+ QVERIFY (line2.isValid ());
3079+ QCOMPARE (line1.textLength (), 1 );
3080+ }
3081+
3082+ void tst_QTextLayout::standaloneInlineObject_eliding ()
3083+ {
3084+ // "A<ORC>B" — total width exceeds availableWidth, so eliding must truncate.
3085+ const QChar orc = QChar::ObjectReplacementCharacter;
3086+ QString text = QStringLiteral (" A" ) + orc + QStringLiteral (" B" );
3087+
3088+ QTextLayout layout;
3089+ setupStandaloneLayoutWithObjects (layout, text, testFont, { 1 }, TEST_OBJECT_WIDTH , 16 );
3090+
3091+ const qreal availableWidth = TESTFONT_SIZE + TEST_OBJECT_WIDTH ;
3092+
3093+ layout.beginLayout ();
3094+ QTextLine line = layout.createLine ();
3095+ line.setLineWidth (availableWidth);
3096+ layout.endLayout ();
3097+
3098+ QString elided = layout.engine ()->elidedText (Qt::ElideRight, QFixed::fromReal (availableWidth),
3099+ 0 , 0 , text.size ());
3100+
3101+ QVERIFY (!elided.isEmpty ());
3102+ QVERIFY (elided.size () < text.size ());
3103+ QVERIFY (elided.contains (QChar (0x2026 )) || elided.contains (QLatin1String (" ..." )));
3104+ }
3105+ #endif
3106+
28763107QTEST_MAIN (tst_QTextLayout)
28773108#include " tst_qtextlayout.moc"
0 commit comments