<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      一種使用iText7渲染引擎去除文字水印方法的過(guò)程記錄

      有一種PDF文本,使用旋轉(zhuǎn)過(guò)的字體來(lái)作為水印。文件經(jīng)過(guò)密碼保護(hù),不能通過(guò)編輯的方法去除。
      轉(zhuǎn)載請(qǐng)保留這一段文字:charset#cnblogs,謝絕CSDN知乎之流轉(zhuǎn)載

      注意:擁有水印并且編輯密碼包含的PDF文檔可能具有版權(quán)保護(hù),本文僅從技術(shù)角度討論可能性。

      正常文件可以被打開(kāi)而且顯示無(wú)誤,使用iText7的渲染引擎來(lái)獲取渲染項(xiàng)目,通過(guò)對(duì)目標(biāo)文本的隱藏來(lái)達(dá)到去除文字水印的目的。

      以下列舉了一些使用過(guò)程中的注意點(diǎn)和坑:

      • 環(huán)境:Windows 11 Home Edition 23H2
      • 機(jī)器:Lenovo L490 i5-8265U@1.6GHz 8C16G
      • 軟件:.NET 8.0.400, RoslynPad 19.1
      1. 引用itext7, 8.0.5, itext7.bouncy-castle-adapter, 8.0.5, itext7.font-asian, 8.0.5,中間用來(lái)解析加密過(guò)的PDF,最后解析亞洲文字。

      2. 寫(xiě)一個(gè)TextExtractionStrategy繼承IEventListener

      class TextExtractionStrategy : IEventListener {
          readonly List<ObjectRenderInfo> info;
          public TextExtractionStrategy(List<ObjectRenderInfo> info) => this.info = info;
          public void EventOccurred(IEventData data, EventType type) {
              switch (data) {
                  case TextRenderInfo renderInfo:
                      info.Add(new ObjectRenderInfo {
                          Text = renderInfo.GetText(),
                          Matrix = renderInfo.GetTextMatrix(),
                          FontName = renderInfo.GetFont(),
                          FontSize = renderInfo.GetFontSize(),
                          Color = renderInfo.GetFillColor(),
                          Width = renderInfo.GetUnscaledWidth()
                      });
                      break;
                  case ImageRenderInfo imageRender:
                      var image = imageRender.GetImage();
                      info.Add(new ObjectRenderInfo { Image = image.GetImageBytes(), Vector = imageRender.GetStartPoint(), Height = image.GetHeight(), Width = image.GetWidth(), Matrix = imageRender.GetImageCtm() });
                      break;
                  case PathRenderInfo pathRender:
                      var operation = pathRender.GetOperation();
                      if (operation != PathRenderInfo.NO_OP) {
                          info.Add(new ObjectRenderInfo {
                              Path = pathRender.GetPath(),
                              Matrix = pathRender.GetCtm(),
                              Width = pathRender.GetGraphicsState().GetLineWidth(),
                              Color = pathRender.GetStrokeColor(),
                              Operation = pathRender.GetOperation(),
                          });
                      }
                      break;
              }
          }
          public ICollection<EventType> GetSupportedEvents() => new List<EventType> { EventType.RENDER_TEXT, EventType.RENDER_IMAGE, EventType.RENDER_PATH };
      }
      

      沒(méi)啥好說(shuō)的,注冊(cè)三種渲染事件,并且在事件回調(diào)的時(shí)候通過(guò)info將傳遞的內(nèi)容記錄下來(lái)。

      List<ObjectRenderInfo> info = new(256);
      var strategy = new TextExtractionStrategy(info);
      var processor = new PdfCanvasProcessor(strategy);
      
      1. 字體的處理

      因?yàn)镻DF的字體直接使用的路不通,所以使用簡(jiǎn)單粗暴的映射本地字體文件的方式進(jìn)行。如果有一些復(fù)式字體不考慮。

      PdfFont GetFont(string name) {
          var fontName = "SimSun.ttc,0";
          if (name.Contains("SimHei", StringComparison.CurrentCultureIgnoreCase)) fontName = "SimHei.ttf";
          else if (name.Contains("Times", StringComparison.CurrentCultureIgnoreCase)) fontName = "times.ttf";
          else if (name.Contains("FangSong", StringComparison.CurrentCultureIgnoreCase)) fontName = "simfang.ttf";
          else if (name.Contains("DengXian", StringComparison.CurrentCultureIgnoreCase)) fontName = "deng.ttf";
          else if (name.Contains("Arial", StringComparison.CurrentCultureIgnoreCase)) fontName = "arial.ttf";
          else if (name.Contains("Verdana", StringComparison.CurrentCultureIgnoreCase)) fontName = "Verdana.ttf";
          else if (name.Contains("KaiTi", StringComparison.CurrentCultureIgnoreCase)) fontName = "simkai.ttf";
          else if (name.Contains("Cambria", StringComparison.CurrentCultureIgnoreCase)) fontName = "Cambria.ttc,0";
          else if (name.Contains("YuGothic", StringComparison.CurrentCultureIgnoreCase)) fontName = "YuGothL.ttc,0";
          else if (name.Contains("Calibri", StringComparison.CurrentCultureIgnoreCase)) fontName = "Calibri.ttf";
          else if (name.Contains("CourierNew", StringComparison.CurrentCultureIgnoreCase)) fontName = "cour.ttf";
          else if (name.Contains("Consolas", StringComparison.CurrentCultureIgnoreCase)) fontName = "consola.ttf";
          if (!fonts.TryGetValue(fontName, out var font)) {
              font = PdfFontFactory.CreateFont($@"C:\Windows\Fonts\{fontName}", PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);
              fonts.Add(fontName, font);
          }
          //Console.Write($"{name} -> {fontName}");
          return font;
      }
      

      注意:每個(gè)PDF需要?jiǎng)?chuàng)建自己的字體實(shí)例,不然保存的時(shí)候會(huì)有異常,引用的資源屬于別的文件。

      1. 渲染過(guò)程
        只有下列三種渲染的方式。
      for (int i = 1; i < docSource.GetNumberOfPages(); i++) {
          info.Clear();
          var page = docSource.GetPage(i);
          //處理原始文件的每一頁(yè)
          processor.ProcessPageContent(page);
          //根據(jù)List<ObjectRenderInfo>內(nèi)容進(jìn)行重新繪制
          foreach (var objRenderInfo in info) {
          }
      }
      

      4.1 渲染文字

      如果需要擦除的水印文字就在這里就很方便的通過(guò)判斷即可。

      var font = GetFont(objRenderInfo.Font.GetFontProgram().GetFontNames().GetFontName());
      var paragraph = new Paragraph(objRenderInfo.Text).SetFixedPosition(i, x, y, objRenderInfo.Width * 2)
          .SetFont(font).SetFontSize(fontSize).SetFontColor(objRenderInfo.Color);
      docTarget.Add(paragraph);
      

      本過(guò)程的靈魂所在就是SetFixedPosition(int pageNumber, float left, float bottom, float width)方法,比對(duì)圖形處理來(lái)說(shuō)會(huì)簡(jiǎn)單一些,直接對(duì)pageNumber指定的頁(yè)進(jìn)行繪制文本操作即可。注意width所指的參數(shù)這里使用了objRenderInfo.Width * 2,試驗(yàn)過(guò)僅用Width可能會(huì)導(dǎo)致文本折行,簡(jiǎn)單起見(jiàn)給定了一個(gè)經(jīng)驗(yàn)值。

      4.2 渲染圖形

      繪制圖形會(huì)比較多的坑。需要注意的幾個(gè)點(diǎn)如下:

      • PdfPage的獲取:Path的繪制需要PdfCanvas,而后者需要從PdfPage創(chuàng)建,顯而易見(jiàn)的想從docTarge.GetPage(i)獲取頁(yè)面實(shí)例,可惜想得太天真了。
      PdfPage? page = null;
      try { page = docTarget.GetPage(i); } catch (Exception) { page = docTarget.AddNewPage(); }
      
      • Matrix轉(zhuǎn)換矩陣的使用:如果簡(jiǎn)單的使用PathRenderInfo的幾個(gè)參數(shù)進(jìn)來(lái)不足以繪制和原先一樣的圖形,是因?yàn)橛衅坪涂s放。
      var offset = new Point(objRenderInfo.Matrix.Get(6), objRenderInfo.Matrix.Get(7));
      float scaleX = objRenderInfo.Matrix.Get(0), scaleY = objRenderInfo.Matrix.Get(4);
      (globalOffset, globalScaleX, globalScaleY) = (offset, scaleX, scaleY);
      
      • globalOffset, globalScaleX, globalScaleY:?jiǎn)为?dú)需要將這幾個(gè)值保存下來(lái)作為本頁(yè)的全局偏移量以及縮放量,是遇到了一些例如流程圖、表格,使用Path繪制的時(shí)候PathRenderInfo記載進(jìn)了Matrix變量。在繪制Shape和上文Text的時(shí)候,需要進(jìn)行計(jì)算。
      //繪制Text
      var x = objRenderInfo.Matrix.Get(6) * globalScaleX + globalOffset.x;
      var y = objRenderInfo.Matrix.Get(7) * globalScaleY + globalOffset.y;
      var fontSize = (float)(objRenderInfo.FontSize * Math.Sqrt(globalScaleX * globalScaleY));
      

      在這里fontSize做了特殊處理,短時(shí)間內(nèi)還沒(méi)法知道到底是X還是Y軸需要縮放。

      //繪制圖形
      foreach (var sub in objRenderInfo.Path.GetSubpaths()) {
          canvas.SaveState();
          foreach (var shape in sub.GetSegments()) {
              switch(shape) {
                  case iText.Kernel.Geom.Line line:
                  //處理直線
                  break;
                  case iText.Kernel.Geom.BezierCurve curve:
                  //處理曲線
                  break;
                  default: Console.Write(shape); break;
              }
          }
          if (sub.IsClosed()) canvas.ClosePath();
          canvas.Stroke();
          canvas.RestoreState();
      }
      
      • 繪制直線:
      var points = line.GetBasePoints();
      canvas.MoveTo(offset.x + points[0].x * scaleX, offset.y + points[0].y * scaleY);
      canvas.LineTo(offset.x + points[1].x * scaleX, offset.y + points[1].y * scaleY);
      
      • 繪制曲線:我遇到的這個(gè)文件里面是3個(gè)點(diǎn)確定一個(gè)曲線,理論上按照文檔也會(huì)有2個(gè)點(diǎn)。以下省略的點(diǎn)個(gè)數(shù)判斷。
      var points = curve.GetBasePoints();
      canvas.MoveTo(offset.x + points[0].x * scaleX, offset.y + points[0].y * scaleY);
      canvas.CurveTo(offsetx + points[1].x * scaleX, offset.y + points[1].y * scaleY,
          offsetx + points[2].x * scaleX, offset.y + points[2].y * scaleY,
          offsetx + points[3].x * scaleX, offset.y + points[3].y * scaleY);
      

      應(yīng)該還存在更簡(jiǎn)單的使用Matrix的API可以縮減代碼量,不過(guò)時(shí)間太少?zèng)]有深入研究

      4.3 渲染圖像
      圖像的繪制相對(duì)簡(jiǎn)單,但是還有一些坑沒(méi)填上。比如獲取的ImageBytes展示出來(lái)是黑塊,在不影響閱讀的情況下還沒(méi)研究修復(fù)。由于直接可以繪制在指定頁(yè)面,所以篇幅會(huì)很小。

      var image = new Image(ImageDataFactory.Create(objRenderInfo.Image))
          .SetFixedPosition(i, objRenderInfo.Vector.Get(0), objRenderInfo.Vector.Get(1));
      if (objRenderInfo.Width > page.GetPageSize().GetWidth())
          image.SetAutoScale(true);
      else
          image.SetWidth(objRenderInfo.Width).SetHeight(objRenderInfo.Height);
      docTarget.Add(image);
      
      1. 后話(huà)
        ObjectRenderInfo的定義
      class ObjectRenderInfo {
          public string? Text { get; set; }
          public PdfFont? FontName { get; set; }
          public float FontSize { get; set; }
          public float Width { get; set; }
          public float Height { get; set; }
          public Color? Color { get; set; }
          public Color? Background { get; set; }
          public Matrix? Matrix { get; set; }
          public byte[]? Image { get; set; }
          public Vector? Vector { get; set; }
          public iText.Kernel.Geom.Path? Path { get; set; }
          public int Operation { get; set; }
      }
      

      使用上述代碼的話(huà),幾乎可以將原先PDF內(nèi)容繪制到新的文件,不過(guò)還存在兩個(gè)問(wèn)題。

      • 一些圖形中帶文本的位置會(huì)亂。目前尚未找到解決方法。
      • 一些圖像展示不出來(lái),僅是一個(gè)黑塊,因?yàn)闆](méi)有分析二進(jìn)制圖像內(nèi)存所以還未找到解決方法。
      posted @ 2024-09-29 22:40  charset  閱讀(267)  評(píng)論(0)    收藏  舉報(bào)
      主站蜘蛛池模板: 奇米777四色影视在线看| 欧美老少配性行为| 屏东县| 麻豆亚洲精品一区二区| 中文国产人精品久久蜜桃| 国产av不卡一区二区| 无限看片在线版免费视频大全| 国产成人亚洲综合91精品| 国产精品有码在线观看| 国产女主播喷水视频在线观看| 99精品国产兔费观看久久99| 国产一区二区三区色视频| 国产女人在线视频| 99精品国产一区二区三区不卡 | 亚洲欧美国产日韩天堂区| 日韩一区精品视频一区二区| 2018年亚洲欧美在线v| 亚洲成在人线av无码| 国产偷自视频区视频| 国产99视频精品免费视频36| 亚洲V天堂V手机在线| 东京热加勒比无码少妇| 亚洲经典在线中文字幕| 精品国产亚洲区久久露脸| 亚洲高清国产拍精品熟女| 中文字幕日本一区二区在线观看 | 亚洲精品乱码久久久久久自慰| 国日韩精品一区二区三区| 亚洲精品男男一区二区| 国产成人无码aa精品一区| 中文字幕一区二区三区久久蜜桃 | 天堂av在线一区二区| 国产亚洲精品久久综合阿香| 爱性久久久久久久久| 内射中出无码护士在线| 欧美人禽杂交狂配| 国产日韩精品一区二区在线观看播放 | 国产玖玖玖玖精品电影| 国产成人精品亚洲一区二区| 蜜桃AV抽搐高潮一区二区| 亚洲精品乱码免费精品乱|