I’m building an in-place PDF editor in Compose Multiplatform. The workflow is similar to Adobe Acrobat: I render the PDF page to a bitmap, extract the text bounds using iText7, and when the user clicks a paragraph, I overlay a white Box and a BasicTextField to let them edit the text live.

The Problem: The final PDF export works flawlessly—the text is placed at the exact right coordinates with the correct font size. However, during live editing, the text inside the Compose BasicTextField looks optically smaller and has different spacing compared to the underlying, rasterized PDF text. It causes a jarring visual "shrink/jump" when the user enters edit mode.

Here is how I am currently extracting the text metrics and applying them to the UI:

  1. Text Extraction (iText7 / PdfEditor.kt) I use PdfCanvasProcessor to get the text matrix. To handle scaled pages (CTM), I calculate the physical height using the ascent and descent lines rather than relying on the raw fontSize.

    actual class PdfEditor { actual suspend fun extractTextBlocks(originalPdf: ByteArray, pageIndex: Int): List<PdfTextFragment> { // ... standard iText reader setup ... val listener = object : IEventListener { override fun eventOccurred(data: IEventData, type: EventType) { if (type == EventType.RENDER_TEXT) { val renderInfo = data as TextRenderInfo

     // Extracting physical height to account for PDF CTM matrices val ascent = renderInfo.ascentLine.startPoint.get(1) val descent = renderInfo.descentLine.startPoint.get(1) val trueVisualHeight = abs(ascent - descent).toFloat() val extractedFontSize = if (trueVisualHeight > 0f) trueVisualHeight else renderInfo.fontSize // Saved to data class to pass to UI... } } override fun getSupportedEvents(): Set<EventType> = EnumSet.of(EventType.RENDER_TEXT) } // ... } 

    }

2. The UI Layer (ArchitectureB_Workspace.kt) This handles the PDF bitmap rendering, scaling, and the Compose Multiplatform BasicTextField overlay.

// Inside BoxWithConstraints: val containerWidthPx = with(LocalDensity.current) { maxWidth.toPx() } val density = LocalDensity.current // Calculate Screen to PDF zoom ratio val pageWidthPx = containerWidthPx * 0.94f val pageHeightPx = pageWidthPx * (pdfDocHeight / pdfDocWidth) val scaleFactor = pageWidthPx / pdfDocWidth // PDF point to Screen Pixel ratio LazyColumn { items(pageCount) { pageIndex -> Box { // 1. Draw static PDF Bitmap Background Image(bitmap = pageBitmaps[pageIndex], contentScale = ContentScale.FillBounds) // 2. Overlay Interactive TextField elements.filter { it.pageIndex == pageIndex }.forEach { element -> if (element is EditorElement.TextElement) { // === THE SCALING MATH === // Scale PDF point size to screen pixels val exactFontPixelSize = element.fontSize * scaleFactor // Neutralize screen density to get accurate SP val visualFontSizeSp = with(density) { exactFontPixelSize.toSp() } val visualLineHeightSp = with(density) { (exactFontPixelSize * 1.15f).toSp() } if (isSelected) { BasicTextField( value = element.textFieldValue, onValueChange = { element.onTextValueChange(it) }, textStyle = TextStyle( fontSize = visualFontSizeSp, lineHeight = visualLineHeightSp, color = Color.Black, lineHeightStyle = LineHeightStyle( alignment = LineHeightStyle.Alignment.Proportional, trim = LineHeightStyle.Trim.Both // Attempting to strip Compose internal padding ) ), modifier = Modifier.fillMaxWidth().wrapContentHeight() ) } } } } } } 

What I know/tried:

  • I'm extracting the exact physical height using iText7's ascentLine and descentLine (to respect PDF scaling matrices), scaling it to screen pixels, and converting to sp. The math is provably correct for the PDF space since the export works.
  • The PDF uses native fonts (Helvetica/Times), while my UI uses downloaded Google Fonts (Arimo/Roboto). I know glyph metrics (Em-squares) differ between font files, making the Skia UI font look smaller.
  • Because this is KMP, I cannot use Android's includeFontPadding = false.

My Question: How can I exactly match the font size, style, and baseline metrics between a PDF vector bounding box and Compose's Skia text layout so they map exactly 1:1 on screen?

Is there a standard formula or TextStyle configuration (like dynamic letter spacing or absolute baseline height) to neutralize these typographic differences when using substitute fonts? Or is the only way to avoid the "jump" to somehow extract the embedded .ttf binaries directly from the PDF?

submitted by /u/Wonderful-Speaker-97
[link] [comments]