public void ShowText(IInputBytes bytes) { var currentState = GetCurrentState(); var font = currentState.FontState.FromExtendedGraphicsState ? activeExtendedGraphicsStateFont : resourceStore.GetFont(currentState.FontState.FontName); if (font == null) { throw new InvalidOperationException($"Could not find the font with name {currentState.FontState.FontName} in the resource store. It has not been loaded yet."); } var fontSize = currentState.FontState.FontSize; var horizontalScaling = currentState.FontState.HorizontalScaling / 100.0; var characterSpacing = currentState.FontState.CharacterSpacing; var rise = currentState.FontState.Rise; var transformationMatrix = currentState.CurrentTransformationMatrix; var renderingMatrix = TransformationMatrix.FromValues(fontSize * horizontalScaling, 0, 0, fontSize, 0, rise); // TODO: this does not seem correct, produces the correct result for now but we need to revisit. // see: https://stackoverflow.com/questions/48010235/pdf-specification-get-font-size-in-points var fontSizeMatrix = transformationMatrix.Multiply(TextMatrices.TextMatrix).Multiply(fontSize); var pointSize = Math.Round(fontSizeMatrix.A, 2); // Assume a rotated letter if (pointSize == 0) { pointSize = Math.Round(fontSizeMatrix.B, 2); } if (pointSize < 0) { pointSize *= -1; } while (bytes.MoveNext()) { var code = font.ReadCharacterCode(bytes, out int codeLength); var foundUnicode = font.TryGetUnicode(code, out var unicode); if (!foundUnicode || unicode == null) { log.Warn($"We could not find the corresponding character with code {code} in font {font.Name}."); // Try casting directly to string as in PDFBox 1.8. unicode = new string((char)code, 1); } var wordSpacing = 0.0; if (code == ' ' && codeLength == 1) { wordSpacing += GetCurrentState().FontState.WordSpacing; } var textMatrix = TextMatrices.TextMatrix; if (font.IsVertical) { if (!(font is IVerticalWritingSupported verticalFont)) { throw new InvalidOperationException($"Font {font.Name} was in vertical writing mode but did not implement {nameof(IVerticalWritingSupported)}."); } var positionVector = verticalFont.GetPositionVector(code); textMatrix = textMatrix.Translate(positionVector.X, positionVector.Y); } var boundingBox = font.GetBoundingBox(code); var transformedGlyphBounds = PerformantRectangleTransformer .Transform(renderingMatrix, textMatrix, transformationMatrix, boundingBox.GlyphBounds); var transformedPdfBounds = PerformantRectangleTransformer .Transform(renderingMatrix, textMatrix, transformationMatrix, new PdfRectangle(0, 0, boundingBox.Width, 0)); if (rotation.Value > 0) { transformedGlyphBounds = rotation.Rotate(transformedGlyphBounds, pageSize); transformedPdfBounds = rotation.Rotate(transformedPdfBounds, pageSize); } // If the text rendering mode calls for filling, the current nonstroking color in the graphics state is used; // if it calls for stroking, the current stroking color is used. // In modes that perform both filling and stroking, the effect is as if each glyph outline were filled and then stroked in separate operations. // TODO: expose color as something more advanced var color = currentState.FontState.TextRenderingMode != TextRenderingMode.Stroke ? currentState.CurrentNonStrokingColor : currentState.CurrentStrokingColor; var letter = new Letter(unicode, transformedGlyphBounds, transformedPdfBounds.BottomLeft, transformedPdfBounds.BottomRight, transformedPdfBounds.Width, fontSize, font.Name.Data, color, pointSize, textSequence); letters.Add(letter); markedContentStack.AddLetter(letter); double tx, ty; if (font.IsVertical) { var verticalFont = (IVerticalWritingSupported)font; var displacement = verticalFont.GetDisplacementVector(code); tx = 0; ty = (displacement.Y * fontSize) + characterSpacing + wordSpacing; } else { tx = (boundingBox.Width * fontSize + characterSpacing + wordSpacing) * horizontalScaling; ty = 0; } TextMatrices.TextMatrix = TextMatrices.TextMatrix.Translate(tx, ty); } }
public void ShowText(IInputBytes bytes) { var currentState = GetCurrentState(); var font = currentState.FontState.FromExtendedGraphicsState ? activeExtendedGraphicsStateFont : resourceStore.GetFont(currentState.FontState.FontName); if (font == null) { throw new InvalidOperationException($"Could not find the font with name {currentState.FontState.FontName} in the resource store. It has not been loaded yet."); } var fontSize = currentState.FontState.FontSize; var horizontalScaling = currentState.FontState.HorizontalScaling / 100.0; var characterSpacing = currentState.FontState.CharacterSpacing; var rise = currentState.FontState.Rise; var transformationMatrix = currentState.CurrentTransformationMatrix; var renderingMatrix = TransformationMatrix.FromValues(fontSize * horizontalScaling, 0, 0, fontSize, 0, rise); var pointSize = Math.Round(transformationMatrix.Multiply(TextMatrices.TextMatrix).Transform(new PdfRectangle(0, 0, 1, fontSize)).Height, 2); while (bytes.MoveNext()) { var code = font.ReadCharacterCode(bytes, out int codeLength); var foundUnicode = font.TryGetUnicode(code, out var unicode); if (!foundUnicode || unicode == null) { log.Warn($"We could not find the corresponding character with code {code} in font {font.Name}."); // Try casting directly to string as in PDFBox 1.8. unicode = new string((char)code, 1); } var wordSpacing = 0.0; if (code == ' ' && codeLength == 1) { wordSpacing += GetCurrentState().FontState.WordSpacing; } var textMatrix = TextMatrices.TextMatrix; if (font.IsVertical) { if (!(font is IVerticalWritingSupported verticalFont)) { throw new InvalidOperationException($"Font {font.Name} was in vertical writing mode but did not implement {nameof(IVerticalWritingSupported)}."); } var positionVector = verticalFont.GetPositionVector(code); textMatrix = textMatrix.Translate(positionVector.X, positionVector.Y); } var boundingBox = font.GetBoundingBox(code); var transformedGlyphBounds = PerformantRectangleTransformer .Transform(renderingMatrix, textMatrix, transformationMatrix, boundingBox.GlyphBounds); var transformedPdfBounds = PerformantRectangleTransformer .Transform(renderingMatrix, textMatrix, transformationMatrix, new PdfRectangle(0, 0, boundingBox.Width, 0)); // If the text rendering mode calls for filling, the current nonstroking color in the graphics state is used; // if it calls for stroking, the current stroking color is used. // In modes that perform both filling and stroking, the effect is as if each glyph outline were filled and then stroked in separate operations. // TODO: expose color as something more advanced var color = currentState.FontState.TextRenderingMode != TextRenderingMode.Stroke ? currentState.CurrentNonStrokingColor : currentState.CurrentStrokingColor; Letter letter = null; if (Diacritics.IsInCombiningDiacriticRange(unicode) && bytes.CurrentOffset > 0 && letters.Count > 0) { var attachTo = letters[letters.Count - 1]; if (attachTo.TextSequence == textSequence && Diacritics.TryCombineDiacriticWithPreviousLetter(unicode, attachTo.Value, out var newLetter)) { // TODO: union of bounding boxes. letters.Remove(attachTo); letter = new Letter( newLetter, attachTo.GlyphRectangle, attachTo.StartBaseLine, attachTo.EndBaseLine, attachTo.Width, attachTo.FontSize, attachTo.Font, attachTo.Color, attachTo.PointSize, attachTo.TextSequence); } else { letter = new Letter( unicode, transformedGlyphBounds, transformedPdfBounds.BottomLeft, transformedPdfBounds.BottomRight, transformedPdfBounds.Width, fontSize, font.Details, color, pointSize, textSequence); } } else { letter = new Letter( unicode, transformedGlyphBounds, transformedPdfBounds.BottomLeft, transformedPdfBounds.BottomRight, transformedPdfBounds.Width, fontSize, font.Details, color, pointSize, textSequence); } letters.Add(letter); markedContentStack.AddLetter(letter); double tx, ty; if (font.IsVertical) { var verticalFont = (IVerticalWritingSupported)font; var displacement = verticalFont.GetDisplacementVector(code); tx = 0; ty = (displacement.Y * fontSize) + characterSpacing + wordSpacing; } else { tx = (boundingBox.Width * fontSize + characterSpacing + wordSpacing) * horizontalScaling; ty = 0; } TextMatrices.TextMatrix = TextMatrices.TextMatrix.Translate(tx, ty); } }